diff options
Diffstat (limited to 'usr/src')
25 files changed, 7482 insertions, 2987 deletions
diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile index 4905a502e7..09dd4bff22 100644 --- a/usr/src/lib/Makefile +++ b/usr/src/lib/Makefile @@ -246,6 +246,7 @@ SUBDIRS += \ sparc_SUBDIRS= .WAIT \ efcode \ libc_psr .WAIT \ + libds \ libdscp \ libprtdiag .WAIT \ libprtdiag_psr \ @@ -448,6 +449,7 @@ $(CLOSED_BUILD)HDRSUBDIRS += \ $(CLOSED)/lib/smartcard sparc_HDRSUBDIRS= \ + libds \ libdscp \ libpri diff --git a/usr/src/lib/libds/Makefile b/usr/src/lib/libds/Makefile new file mode 100644 index 0000000000..059b9076c4 --- /dev/null +++ b/usr/src/lib/libds/Makefile @@ -0,0 +1,58 @@ +# +# 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. +# +# + +include ../Makefile.lib + +HDRS = libds.h +HDRDIR = common + +ROOTHDRDIR = $(ROOT)/usr/platform/sun4v/include/sys + +SUBDIRS = $(MACH) +$(BUILD64)SUBDIRS += $(MACH64) + +all := TARGET = all +clean := TARGET = clean +clobber := TARGET = clobber +install := TARGET = install +lint := TARGET = lint + +.KEEP_STATE: + +all clean clobber lint: $(SUBDIRS) + +check: $(CHECKHDRS) + +install: all .WAIT $(SUBDIRS) + +install_h: $(ROOTHDRS) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include ../Makefile.targ diff --git a/usr/src/lib/libds/Makefile.com b/usr/src/lib/libds/Makefile.com new file mode 100644 index 0000000000..9168e65ed2 --- /dev/null +++ b/usr/src/lib/libds/Makefile.com @@ -0,0 +1,64 @@ +# +# 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. +# +# + +LIBRARY = libds.a +VERS = .1 + +LIBSRCS = libds.c +OBJECTS = $(LIBSRCS:%.c=%.o) + +include ../../Makefile.lib + +LIBS = $(DYNLIB) $(LINTLIB) + +SRCDIR = ../common +SRCS = $(LIBSRCS:%.c=$(SRCDIR)/%.c) + +CPPFLAGS += -I. -I$(SRC)/uts/sun4v +CFLAGS += $(CCVERBOSE) $(C_BIGPICFLAGS) +CFLAGS64 += $(CCVERBOSE) $(C_BIGPICFLAGS) + +LDLIBS += -lsysevent -lnvpair -lc + +LINTFLAGS = -msux +LINTFLAGS64 = -msux -Xarch=$(MACH64:sparcv9=v9) + +$(LINTLIB) := SRCS = $(LINTSRC:%=$(SRCDIR)/%) +$(LINTLIB) := LINTFLAGS = -nsvx -I$(ROOT)/usr/platform/sun4v/include +$(LINTLIB) := LINTFLAGS64 = -nsvx -Xarch=$(MACH64:sparcv9=v9) \ + -I$(ROOT)/usr/platform/sun4v/include + +.KEEP_STATE: + +all: $(LIBS) + +lint: $(LINTLIB) lintcheck + +pics/%.o: $(SRCDIR)/%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) + +include ../../Makefile.targ diff --git a/usr/src/lib/libds/common/libds.c b/usr/src/lib/libds/common/libds.c new file mode 100644 index 0000000000..bacdd14f5a --- /dev/null +++ b/usr/src/lib/libds/common/libds.c @@ -0,0 +1,710 @@ +/* + * 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. + */ + +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <strings.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/sysevent.h> +#include <libsysevent.h> +#include <sys/vlds.h> +#include "libds.h" + +#define PTRTOUINT64(ptr) ((uint64_t)((uintptr_t)(ptr))) +static char vlds_device[] = + "/devices/virtual-devices@100/channel-devices@200/" + "virtual-domain-service@0:vlds"; + +typedef struct dslibentry { + ds_hdl_t dsl_hdl; + uint32_t dsl_flags; + char *dsl_service; + ds_ops_t dsl_ops; +} dslibentry_t; + +#define MIN_DSLIB_ENTRIES 64 +static dslibentry_t *dslibtab; +static int ndslib; + +/* + * Lock to protect the dslibtab table. We only need to protect this + * table for those functions which actually look at or modify the table: + * service registration (ds_svc_reg/ds_clnt_reg), service unregistration + * (ds_hdl_unreg) or during callbacks (ds_recv) + */ +static mutex_t dslib_lock; + +static int ds_fd = -1; + +static char *ds_sid_name = "vlds"; + +static evchan_t *ds_evchan; + +/* + * Static functions internal to dslib. + */ +static dslibentry_t *ds_hdl_to_dslibentry(ds_hdl_t hdl); +static dslibentry_t *ds_lookup_dslibentry(char *service, boolean_t is_client); +static dslibentry_t *ds_register_dslibentry(ds_hdl_t hdl, char *service, + boolean_t is_client); +static void ds_free_dslibentry(dslibentry_t *dsp, int force_unreg); +static int ds_recv(sysevent_t *sep, void *arg); +static void ds_string_arg(vlds_string_t *dsp, char *str); +static int ds_register(ds_capability_t *cap, ds_ops_t *ops, uint_t flags); + +static dslibentry_t * +ds_hdl_to_dslibentry(ds_hdl_t hdl) +{ + int i; + dslibentry_t *dsp; + + for (i = 0, dsp = dslibtab; i < ndslib; i++, dsp++) { + if (hdl == dsp->dsl_hdl) + return (dsp); + } + return (NULL); +} + +static dslibentry_t * +ds_new_dslibentry(void) +{ + int newndslib; + dslibentry_t *dsp; + + if ((dsp = ds_hdl_to_dslibentry(NULL)) != NULL) + return (dsp); + + /* double the size */ + newndslib = ndslib << 1; + if ((dslibtab = realloc(dslibtab, newndslib * sizeof (dslibentry_t))) + == NULL) + return (NULL); + dsp = &dslibtab[ndslib]; + (void) memset(dsp, 0, (newndslib - ndslib) * sizeof (dslibentry_t)); + ndslib = newndslib; + return (dsp); +} + +static dslibentry_t * +ds_lookup_dslibentry(char *service, boolean_t is_client) +{ + int i; + dslibentry_t *dsp; + uint_t is_client_flag = is_client ? VLDS_REG_CLIENT : 0; + + for (i = 0, dsp = dslibtab; i < ndslib; i++, dsp++) { + if (dsp->dsl_hdl != NULL && + strcmp(dsp->dsl_service, service) == 0 && + (dsp->dsl_flags & VLDS_REG_CLIENT) == is_client_flag) { + return (dsp); + } + } + return (NULL); +} + +static dslibentry_t * +ds_register_dslibentry(ds_hdl_t hdl, char *service, boolean_t is_client) +{ + dslibentry_t *dsp, *orig_dsp; + uint_t nhdls; + + if ((dsp = ds_hdl_to_dslibentry(hdl)) != NULL) + return (dsp); + + if ((orig_dsp = ds_lookup_dslibentry(service, is_client)) == NULL) { + return (NULL); + } + + /* + * Find out if we have 1 or 2 or more handles. Having one implies + * that we can reuse the current one handle for this service. + * Having two or more implies we need to allocate a new handle. + */ + if (ds_hdl_lookup(service, is_client, NULL, 2, &nhdls) != 0) + return (NULL); + + if (nhdls == 1) { + /* reuse the original structure entry */ + dsp = orig_dsp; + } else if (nhdls == 2) { + /* allocate a new structure entry */ + if ((dsp = ds_new_dslibentry()) == NULL) + return (NULL); + *dsp = *orig_dsp; + dsp->dsl_service = strdup(orig_dsp->dsl_service); + } else { + /* can't happen... */ + return (NULL); + } + dsp->dsl_hdl = hdl; + return (dsp); +} + +/* + * Want to leave an entry in the dslib table even though all the + * handles may have been unregistered for it. + */ +static void +ds_free_dslibentry(dslibentry_t *dsp, int force_unreg) +{ + uint_t nhdls; + + /* + * Find out if we have 1 or 2 or more handles. Having one implies + * that we want to leave the entry alone unless this is a ds_unreg_hdl + * (force_unreg is true). + */ + if (ds_hdl_lookup(dsp->dsl_service, + (dsp->dsl_flags & VLDS_REG_CLIENT) != 0, NULL, 2, &nhdls) != 0) { + /* should never happen */ + return; + } + + if ((nhdls == 1 && force_unreg) || nhdls == 2) { + dsp->dsl_hdl = NULL; + if (dsp->dsl_service) { + free(dsp->dsl_service); + } + (void) memset(dsp, 0, sizeof (dslibentry_t)); + } +} + +/*ARGSUSED*/ +static int +ds_recv(sysevent_t *sep, void *arg) +{ + nvlist_t *nvl; + uint64_t hdl; + ds_ver_t ver; + ds_domain_hdl_t dhdl; + uchar_t *bufp; + boolean_t is_client; + uint_t buflen; + char *subclass; + char *servicep; + dslibentry_t *dsp; + ds_cb_arg_t cb_arg; + + subclass = sysevent_get_subclass_name(sep); + if (sysevent_get_attr_list(sep, &nvl) != 0) { + return (0); + } + + if (nvlist_lookup_uint64(nvl, VLDS_HDL, &hdl) == 0) { + if (strcmp(subclass, ESC_VLDS_REGISTER) == 0) { + void (*reg_cb)(ds_hdl_t, ds_cb_arg_t, ds_ver_t *, + ds_domain_hdl_t) = NULL; + + if (nvlist_lookup_string(nvl, VLDS_SERVICE_ID, + &servicep) == 0 && + nvlist_lookup_boolean_value(nvl, VLDS_ISCLIENT, + &is_client) == 0) { + (void) mutex_lock(&dslib_lock); + if ((dsp = ds_register_dslibentry(hdl, + servicep, is_client)) != NULL) { + reg_cb = dsp->dsl_ops.ds_reg_cb; + cb_arg = dsp->dsl_ops.cb_arg; + } + (void) mutex_unlock(&dslib_lock); + if (reg_cb != NULL && + nvlist_lookup_uint64(nvl, VLDS_DOMAIN_HDL, + &dhdl) == 0 && + nvlist_lookup_uint16(nvl, VLDS_VER_MAJOR, + &ver.major) == 0 && + nvlist_lookup_uint16(nvl, VLDS_VER_MINOR, + &ver.minor) == 0) { + (reg_cb)((ds_hdl_t)hdl, cb_arg, &ver, + dhdl); + } + } + } else if (strcmp(subclass, ESC_VLDS_UNREGISTER) == 0) { + void (*unreg_cb)(ds_hdl_t, ds_cb_arg_t) = NULL; + + (void) mutex_lock(&dslib_lock); + if ((dsp = ds_hdl_to_dslibentry(hdl)) != NULL) { + unreg_cb = dsp->dsl_ops.ds_unreg_cb; + cb_arg = dsp->dsl_ops.cb_arg; + ds_free_dslibentry(dsp, 0); + } + (void) mutex_unlock(&dslib_lock); + if (unreg_cb != NULL) { + (unreg_cb)((ds_hdl_t)hdl, cb_arg); + } + } else if (strcmp(subclass, ESC_VLDS_DATA) == 0) { + void (*data_cb)(ds_hdl_t, ds_cb_arg_t, void *, + size_t) = NULL; + + (void) mutex_lock(&dslib_lock); + if ((dsp = ds_hdl_to_dslibentry(hdl)) != NULL) { + data_cb = dsp->dsl_ops.ds_data_cb; + cb_arg = dsp->dsl_ops.cb_arg; + } + (void) mutex_unlock(&dslib_lock); + if (data_cb != NULL && + nvlist_lookup_byte_array(nvl, VLDS_DATA, &bufp, + &buflen) == 0) { + (data_cb)((ds_hdl_t)hdl, cb_arg, bufp, buflen); + } + } + } + nvlist_free(nvl); + return (0); +} + +static void +ds_string_arg(vlds_string_t *dsp, char *str) +{ + if (str == NULL) { + dsp->vlds_strp = NULL; + dsp->vlds_strlen = 0; + } else { + dsp->vlds_strp = PTRTOUINT64(str); + dsp->vlds_strlen = strlen(str) + 1; + } +} + +static int +ds_init_sysev(void) +{ + char evchan_name[MAX_CHNAME_LEN]; + + (void) sprintf(evchan_name, VLDS_SYSEV_CHAN_FMT, (int)getpid()); + if (sysevent_evc_bind(evchan_name, &ds_evchan, 0) != 0) { + return (errno); + } + if (sysevent_evc_subscribe(ds_evchan, ds_sid_name, EC_VLDS, + ds_recv, NULL, 0) != 0) { + sysevent_evc_unbind(ds_evchan); + ds_evchan = NULL; + return (errno); + } + return (0); +} + +int +ds_init(void) +{ + if (ds_fd >= 0) + return (0); + + if ((ds_fd = open(vlds_device, 0)) < 0) + return (errno); + + if (dslibtab == NULL) { + if ((dslibtab = malloc(sizeof (dslibentry_t) * ndslib)) == NULL) + return (errno = ENOMEM); + ndslib = MIN_DSLIB_ENTRIES; + (void) memset(dslibtab, 0, sizeof (dslibentry_t) * ndslib); + } + + (void) mutex_init(&dslib_lock, USYNC_THREAD, NULL); + return (0); +} + +static int +ds_register(ds_capability_t *cap, ds_ops_t *ops, uint_t flags) +{ + dslibentry_t *dsp; + vlds_svc_reg_arg_t vlds_arg; + vlds_cap_t vlds_cap; + vlds_ver_t vlds_vers[VLDS_MAX_VERS]; + uint64_t hdl_arg; + ds_hdl_t hdl; + uint_t nhdls; + int i; + + if (cap == NULL || ops == NULL || cap->svc_id == NULL || + cap->vers == NULL || (flags & (~VLDS_REG_CLIENT)) != 0) { + return (errno = EINVAL); + } + + if (cap->nvers > VLDS_MAX_VERS) { + return (errno = EINVAL); + } + + if (ds_fd < 0 && (errno = ds_init()) != 0) { + return (errno); + } + + if (ds_hdl_lookup(cap->svc_id, (flags & VLDS_REG_CLIENT), NULL, 1, + &nhdls) == 0 && nhdls == 1) { + return (errno = EALREADY); + } + + (void) mutex_lock(&dslib_lock); + if ((dsp = ds_new_dslibentry()) == NULL) { + (void) mutex_unlock(&dslib_lock); + return (errno = ENOMEM); + } + + /* Setup device driver capability structure. */ + + /* service string */ + ds_string_arg(&vlds_cap.vlds_service, cap->svc_id); + + /* version array */ + for (i = 0; i < cap->nvers; i++) { + vlds_vers[i].vlds_major = cap->vers[i].major; + vlds_vers[i].vlds_minor = cap->vers[i].minor; + } + vlds_cap.vlds_versp = PTRTOUINT64(vlds_vers); + vlds_cap.vlds_nver = cap->nvers; + + /* + * Format args for VLDS_SVC_REG ioctl. + */ + + vlds_arg.vlds_capp = PTRTOUINT64(&vlds_cap); + + /* op flags */ + if (ops->ds_reg_cb != NULL) + flags |= VLDS_REGCB_VALID; + if (ops->ds_unreg_cb != NULL) + flags |= VLDS_UNREGCB_VALID; + if (ops->ds_data_cb != NULL) + flags |= VLDS_DATACB_VALID; + vlds_arg.vlds_reg_flags = flags; + + /* returned handle */ + vlds_arg.vlds_hdlp = PTRTOUINT64(&hdl_arg); + + if (ioctl(ds_fd, VLDS_SVC_REG, &vlds_arg) < 0) { + (void) mutex_unlock(&dslib_lock); + return (errno); + } + + /* + * Setup user callback sysevent channel. + */ + if ((flags & VLDS_ANYCB_VALID) != 0 && ds_evchan == NULL && + ds_init_sysev() != 0) { + (void) mutex_unlock(&dslib_lock); + (void) ioctl(ds_fd, VLDS_UNREG_HDL, &vlds_arg); + return (errno); + } + + hdl = hdl_arg; + + /* + * Set entry values in dslibtab. + */ + dsp->dsl_hdl = hdl; + dsp->dsl_flags = flags; + dsp->dsl_service = strdup(cap->svc_id); + dsp->dsl_ops = *ops; + (void) mutex_unlock(&dslib_lock); + return (0); +} + +/* + * Registers a service provider. Kicks off the handshake with other + * domain(s) to announce servce. Callback events are as described above. + */ +int +ds_svc_reg(ds_capability_t *cap, ds_ops_t *ops) +{ + return (ds_register(cap, ops, 0)); +} + +/* + * Registers interest in a service from a specific domain. When that + * service is registered, the register callback is invoked. When that + * service is unregistered, the unregister callback is invoked. When + * data is received, the receive data callback is invoked. + */ +int +ds_clnt_reg(ds_capability_t *cap, ds_ops_t *ops) +{ + return (ds_register(cap, ops, VLDS_REG_CLIENT)); +} + +/* + * Given a service name and type, returns the existing handle(s), if + * one or more exist. This could be used to poll for the connection being + * registered or unregistered, rather than using the register/unregister + * callbacks. + */ +int +ds_hdl_lookup(char *service, boolean_t is_client, ds_hdl_t *hdlsp, + uint_t maxhdls, uint_t *nhdlsp) +{ + vlds_hdl_lookup_arg_t vlds_arg; + uint64_t nhdls_arg; + + errno = 0; + if (ds_fd < 0) { + return (errno = EBADF); + } + + if (service == NULL) { + return (errno = EINVAL); + } + + ds_string_arg(&vlds_arg.vlds_service, service); + vlds_arg.vlds_isclient = is_client ? VLDS_REG_CLIENT : 0; + vlds_arg.vlds_hdlsp = PTRTOUINT64(hdlsp); + vlds_arg.vlds_maxhdls = maxhdls; + vlds_arg.vlds_nhdlsp = PTRTOUINT64(&nhdls_arg); + + if (ioctl(ds_fd, VLDS_HDL_LOOKUP, &vlds_arg) < 0) { + return (errno); + } + + *nhdlsp = nhdls_arg; + return (0); +} + +/* + * Given a handle, return its associated domain. + */ +int +ds_domain_lookup(ds_hdl_t hdl, ds_domain_hdl_t *dhdlp) +{ + vlds_dmn_lookup_arg_t vlds_arg; + uint64_t dhdl_arg; + + if (ds_fd < 0) { + return (errno = EBADF); + } + + vlds_arg.vlds_hdl = hdl; + vlds_arg.vlds_dhdlp = PTRTOUINT64(&dhdl_arg); + + if (ioctl(ds_fd, VLDS_DMN_LOOKUP, &vlds_arg) < 0) { + return (errno); + } + + if (dhdlp) { + *dhdlp = dhdl_arg; + } + + return (0); +} + +/* + * Unregisters either a service or an interest in that service + * indicated by the supplied handle. + */ +int +ds_unreg_hdl(ds_hdl_t hdl) +{ + dslibentry_t *dsp; + vlds_unreg_hdl_arg_t vlds_arg; + + (void) mutex_lock(&dslib_lock); + if ((dsp = ds_hdl_to_dslibentry(hdl)) != NULL) { + ds_free_dslibentry(dsp, 1); + } + (void) mutex_unlock(&dslib_lock); + + if (ds_fd >= 0) { + vlds_arg.vlds_hdl = hdl; + (void) ioctl(ds_fd, VLDS_UNREG_HDL, &vlds_arg); + } + + return (0); +} + +/* + * Send data to the appropriate service provider or client + * indicated by the provided handle. The sender will block + * until the message has been sent. There is no guarantee + * that multiple calls to ds_send_msg by the same thread + * will result in the data showing up at the receiver in + * the same order as sent. If multiple messages are required, + * it will be up to the sender and receiver to implement a + * protocol. + */ +int +ds_send_msg(ds_hdl_t hdl, void *buf, size_t buflen) +{ + vlds_send_msg_arg_t vlds_arg; + + if (ds_fd < 0) { + return (errno = EBADF); + } + + vlds_arg.vlds_hdl = hdl; + vlds_arg.vlds_bufp = PTRTOUINT64(buf); + vlds_arg.vlds_buflen = buflen; + + if (ioctl(ds_fd, VLDS_SEND_MSG, &vlds_arg) < 0) { + return (errno); + } + + return (0); +} + +/* + * Receive data from the appropriate service provider or client + * indicated by the provided handle. The sender will block + * until a message has been received. + */ +int +ds_recv_msg(ds_hdl_t hdl, void *buf, size_t buflen, size_t *msglen) +{ + vlds_recv_msg_arg_t vlds_arg; + uint64_t msglen_arg; + + if (ds_fd < 0) { + return (errno = EBADF); + } + + vlds_arg.vlds_hdl = hdl; + vlds_arg.vlds_bufp = PTRTOUINT64(buf); + vlds_arg.vlds_buflen = buflen; + vlds_arg.vlds_msglenp = PTRTOUINT64(&msglen_arg); + + if (ioctl(ds_fd, VLDS_RECV_MSG, &vlds_arg) < 0) { + if (errno == EFBIG && msglen) { + *msglen = msglen_arg; + } + return (errno); + } + + if (msglen) { + *msglen = msglen_arg; + } + + return (0); +} + +int +ds_isready(ds_hdl_t hdl, boolean_t *is_ready) +{ + vlds_hdl_isready_arg_t vlds_arg; + uint64_t is_ready_arg; + + if (ds_fd < 0) { + return (errno = EBADF); + } + + vlds_arg.vlds_hdl = hdl; + vlds_arg.vlds_isreadyp = PTRTOUINT64(&is_ready_arg); + + if (ioctl(ds_fd, VLDS_HDL_ISREADY, &vlds_arg) < 0) { + return (errno); + } + + *is_ready = (is_ready_arg != 0); + return (0); +} + +/* + * Given a domain name, return its associated domain handle. + */ +int +ds_dom_name_to_hdl(char *domain_name, ds_domain_hdl_t *dhdlp) +{ + vlds_dom_nam2hdl_arg_t vlds_arg; + uint64_t dhdl_arg; + + if (ds_fd < 0) { + return (errno = EBADF); + } + + ds_string_arg(&vlds_arg.vlds_domain_name, domain_name); + vlds_arg.vlds_dhdlp = PTRTOUINT64(&dhdl_arg); + + if (ioctl(ds_fd, VLDS_DOM_NAM2HDL, &vlds_arg) < 0) { + return (errno); + } + + if (dhdlp) { + *dhdlp = dhdl_arg; + } + + return (0); +} + +/* + * Given a domain handle, return its associated domain name. + */ +int +ds_dom_hdl_to_name(ds_domain_hdl_t dhdl, char *domain_name, uint_t maxnamlen) +{ + vlds_dom_hdl2nam_arg_t vlds_arg; + + if (ds_fd < 0) { + return (errno = EBADF); + } + + vlds_arg.vlds_dhdl = dhdl; + vlds_arg.vlds_domain_name.vlds_strp = PTRTOUINT64(domain_name); + vlds_arg.vlds_domain_name.vlds_strlen = maxnamlen; + + if (ioctl(ds_fd, VLDS_DOM_HDL2NAM, &vlds_arg) < 0) { + return (errno); + } + + return (0); +} + +void +ds_unreg_svc(char *service, boolean_t is_client) +{ + ds_hdl_t hdl; + uint_t nhdls; + + while (ds_hdl_lookup(service, is_client, &hdl, 1, &nhdls) == 0 && + nhdls == 1) { + (void) ds_unreg_hdl(hdl); + } +} + +void +ds_fini(void) +{ + int i; + dslibentry_t *dsp; + + if (ds_fd >= 0) { + (void) close(ds_fd); + ds_fd = -1; + } + if (ds_evchan) { + (void) sysevent_evc_unsubscribe(ds_evchan, ds_sid_name); + (void) sysevent_evc_unbind(ds_evchan); + ds_evchan = NULL; + } + if (ndslib > 0) { + (void) mutex_lock(&dslib_lock); + for (i = 0, dsp = dslibtab; i < ndslib; i++, dsp++) { + if (dsp->dsl_hdl == NULL) + continue; + if (dsp->dsl_service) { + free(dsp->dsl_service); + } + } + free(dslibtab); + ndslib = 0; + dslibtab = NULL; + (void) mutex_unlock(&dslib_lock); + (void) mutex_destroy(&dslib_lock); + } +} diff --git a/usr/src/lib/libds/common/libds.h b/usr/src/lib/libds/common/libds.h new file mode 100644 index 0000000000..149e53c91a --- /dev/null +++ b/usr/src/lib/libds/common/libds.h @@ -0,0 +1,95 @@ +/* + * 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. + */ + +#ifndef _LIBDS_H +#define _LIBDS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +/* + * LDOMs User Domain Services library Interfaces + */ + +typedef uint64_t ds_hdl_t; /* service handle */ +typedef uint64_t ds_domain_hdl_t; /* domain handle */ +typedef void *ds_cb_arg_t; /* client callback arg */ + +#define DS_INVALID_HDL (0) /* a ds handle cannot be zero */ + +/* + * LDOMs User Domain Services versioning + */ +typedef struct ds_ver { + uint16_t major; + uint16_t minor; +} ds_ver_t; + +/* + * LDOMs User Domain Services capability + */ +typedef struct ds_capability { + char *svc_id; /* service identifier */ + ds_ver_t *vers; /* list of supported versions */ + uint_t nvers; /* number of supported versions */ +} ds_capability_t; + +/* + * LDOMs User Domain Services event callbacks + */ +typedef struct ds_ops { + void (*ds_reg_cb)(ds_hdl_t hdl, ds_cb_arg_t arg, ds_ver_t *ver, + ds_domain_hdl_t dhdl); + void (*ds_unreg_cb)(ds_hdl_t hdl, ds_cb_arg_t arg); + void (*ds_data_cb)(ds_hdl_t hdl, ds_cb_arg_t arg, void *buf, + size_t buflen); + ds_cb_arg_t cb_arg; +} ds_ops_t; + +extern int ds_init(void); +extern int ds_svc_reg(ds_capability_t *cap, ds_ops_t *ops); +extern int ds_clnt_reg(ds_capability_t *cap, ds_ops_t *ops); +extern int ds_hdl_lookup(char *service, boolean_t is_client, ds_hdl_t *hdlsp, + uint_t maxhdls, uint_t *nhdlsp); +extern int ds_domain_lookup(ds_hdl_t hdl, ds_domain_hdl_t *dhdlp); +extern int ds_unreg_hdl(ds_hdl_t hdl); +extern int ds_send_msg(ds_hdl_t hdl, void *buf, size_t buflen); +extern int ds_recv_msg(ds_hdl_t hdl, void *buf, size_t buflen, + size_t *msglen); +extern int ds_isready(ds_hdl_t hdl, boolean_t *is_ready); +extern int ds_dom_name_to_hdl(char *domain_name, ds_domain_hdl_t *dhdlp); +extern int ds_dom_hdl_to_name(ds_domain_hdl_t dhdl, char *domain_name, + uint_t maxnamlen); +extern void ds_unreg_svc(char *service, boolean_t is_client); +extern void ds_fini(void); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBDS_H */ diff --git a/usr/src/lib/libds/common/llib-lds b/usr/src/lib/libds/common/llib-lds new file mode 100644 index 0000000000..83867420d8 --- /dev/null +++ b/usr/src/lib/libds/common/llib-lds @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include "libds.h" diff --git a/usr/src/lib/libds/common/mapfile-vers b/usr/src/lib/libds/common/mapfile-vers new file mode 100644 index 0000000000..889d1c47f0 --- /dev/null +++ b/usr/src/lib/libds/common/mapfile-vers @@ -0,0 +1,44 @@ +# +# 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. +# +# + +SUNWprivate { + global: + ds_init; + ds_svc_reg; + ds_clnt_reg; + ds_hdl_lookup; + ds_domain_lookup; + ds_unreg_hdl; + ds_send_msg; + ds_recv_msg; + ds_isready; + ds_dom_name_to_hdl; + ds_dom_hdl_to_name; + ds_unreg_svc; + ds_fini; + local: + *; +}; diff --git a/usr/src/lib/libds/sparc/Makefile b/usr/src/lib/libds/sparc/Makefile new file mode 100644 index 0000000000..ea09428ea0 --- /dev/null +++ b/usr/src/lib/libds/sparc/Makefile @@ -0,0 +1,29 @@ +# +# 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. +# +# + +include ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libds/sparcv9/Makefile b/usr/src/lib/libds/sparcv9/Makefile new file mode 100644 index 0000000000..40e4cef06c --- /dev/null +++ b/usr/src/lib/libds/sparcv9/Makefile @@ -0,0 +1,30 @@ +# +# 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. +# +# + +include ../Makefile.com +include ../../Makefile.lib.64 + +install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT64) diff --git a/usr/src/pkgdefs/SUNWldomr.v/postinstall b/usr/src/pkgdefs/SUNWldomr.v/postinstall index ac5b3a9c67..aefe149431 100644 --- a/usr/src/pkgdefs/SUNWldomr.v/postinstall +++ b/usr/src/pkgdefs/SUNWldomr.v/postinstall @@ -21,10 +21,9 @@ # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" # # Function: check_add_drv() @@ -127,6 +126,7 @@ check_add_drv() check_add_drv -b "${BASEDIR}" -i '"SUNW,sun4v-channel-devices"' cnex check_add_drv -b "${BASEDIR}" drctl +check_add_drv -b "${BASEDIR}" -i '"SUNW,sun4v-domain-service"' vlds check_add_drv -b "${BASEDIR}" -i '"SUNW,sun4v-console-concentrator"' vcc check_add_drv -b "${BASEDIR}" -i '"SUNW,sun4v-disk"' vdc check_add_drv -b "${BASEDIR}" -i '"SUNW,sun4v-disk-server"' vds diff --git a/usr/src/pkgdefs/SUNWldomr.v/preremove b/usr/src/pkgdefs/SUNWldomr.v/preremove index 0b99d3f15b..898a7de959 100644 --- a/usr/src/pkgdefs/SUNWldomr.v/preremove +++ b/usr/src/pkgdefs/SUNWldomr.v/preremove @@ -20,10 +20,9 @@ # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" # PATH=/usr/bin:/usr/sbin:${PATH} @@ -49,6 +48,7 @@ not_installed() # not_installed cnex || rem_drv -b "${BASEDIR}" cnex || EXIT=1 not_installed drctl || rem_drv -b "${BASEDIR}" drctl || EXIT=1 +not_installed vlds || rem_drv -b "${BASEDIR}" vlds || EXIT=1 not_installed vcc || rem_drv -b "${BASEDIR}" vcc || EXIT=1 not_installed vdc || rem_drv -b "${BASEDIR}" vdc || EXIT=1 not_installed vds || rem_drv -b "${BASEDIR}" vds || EXIT=1 diff --git a/usr/src/pkgdefs/SUNWldomr.v/prototype_sparc b/usr/src/pkgdefs/SUNWldomr.v/prototype_sparc index 825734cc4a..0b9d9fceef 100644 --- a/usr/src/pkgdefs/SUNWldomr.v/prototype_sparc +++ b/usr/src/pkgdefs/SUNWldomr.v/prototype_sparc @@ -23,7 +23,6 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" # # This required package information file contains a list of package contents. # The 'pkgmk' command uses this file to identify the contents of a package @@ -65,6 +64,7 @@ f none platform/sun4v/kernel/drv/sparcv9/cnex 755 root sys f none platform/sun4v/kernel/drv/sparcv9/drctl 755 root sys f none platform/sun4v/kernel/drv/sparcv9/ds_pri 755 root sys f none platform/sun4v/kernel/drv/sparcv9/ds_snmp 755 root sys +f none platform/sun4v/kernel/drv/sparcv9/vlds 755 root sys f none platform/sun4v/kernel/drv/sparcv9/vcc 755 root sys f none platform/sun4v/kernel/drv/sparcv9/vdc 755 root sys f none platform/sun4v/kernel/drv/sparcv9/vds 755 root sys diff --git a/usr/src/pkgdefs/SUNWldomu.v/prototype_sparc b/usr/src/pkgdefs/SUNWldomu.v/prototype_sparc index 5b7dd635bb..413badcb41 100644 --- a/usr/src/pkgdefs/SUNWldomu.v/prototype_sparc +++ b/usr/src/pkgdefs/SUNWldomu.v/prototype_sparc @@ -23,7 +23,6 @@ # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" # # This required package information file contains a list of package contents. # The 'pkgmk' command uses this file to identify the contents of a package @@ -54,8 +53,10 @@ d none usr/lib/ldoms 755 root bin f none usr/lib/ldoms/drd 555 root bin f none usr/lib/ldoms/vntsd 555 root bin f none usr/lib/libpri.so.1 755 root bin +f none usr/lib/libds.so.1 755 root bin d none usr/lib/rcm 755 root bin d none usr/lib/rcm/scripts 755 root bin f none usr/lib/rcm/scripts/SUNW,vdevices.pl 555 root bin d none usr/lib/sparcv9 755 root bin f none usr/lib/sparcv9/libpri.so.1 755 root bin +f none usr/lib/sparcv9/libds.so.1 755 root bin diff --git a/usr/src/pkgdefs/etc/exception_list_sparc b/usr/src/pkgdefs/etc/exception_list_sparc index bfbd483227..f01b8e1277 100644 --- a/usr/src/pkgdefs/etc/exception_list_sparc +++ b/usr/src/pkgdefs/etc/exception_list_sparc @@ -977,6 +977,20 @@ usr/lib/llib-lpri.ln sparc usr/lib/sparcv9/libpri.so sparc usr/lib/sparcv9/llib-lpri.ln sparc # +# These files are installed in the proto area by the build of libds for +# the benefit of the builds of sun4v IO FMA and/or other libds +# consumers. However, the libds interfaces are private to Sun +# (Consolidation Private) and not intended for customer use. So these +# files (the symlink and the lint library) are excluded from packaging. +# +usr/lib/libds.so sparc +usr/lib/llib-lds sparc +usr/lib/llib-lds.ln sparc +usr/lib/sparcv9/libds.so sparc +usr/lib/sparcv9/llib-lds.ln sparc +usr/platform/sun4v/include/sys/vlds.h sparc +usr/platform/sun4v/include/sys/libds.h sparc +# # Private/Internal u8_textprep header file. Do not ship. # usr/include/sys/u8_textprep_data.h sparc diff --git a/usr/src/uts/sun4v/Makefile.files b/usr/src/uts/sun4v/Makefile.files index 4055ca40a2..b237268bf9 100644 --- a/usr/src/uts/sun4v/Makefile.files +++ b/usr/src/uts/sun4v/Makefile.files @@ -151,6 +151,7 @@ VDC_OBJS = vdc.o VDS_OBJS = vds.o DS_PRI_OBJS = ds_pri.o DS_SNMP_OBJS = ds_snmp.o +VLDS_OBJS = vlds.o # # Misc modules @@ -159,7 +160,7 @@ BOOTDEV_OBJS += bootdev.o DR_CPU_OBJS += dr_cpu.o DR_IO_OBJS += dr_io.o DRCTL_OBJS = drctl.o drctl_impl.o dr_util.o -DS_OBJS = ds.o +DS_OBJS = ds_common.o ds_drv.o FAULT_ISO_OBJS = fault_iso.o OBPSYM_OBJS += obpsym.o obpsym_1275.o PLATSVC_OBJS = platsvc.o mdeg.o diff --git a/usr/src/uts/sun4v/Makefile.sun4v.shared b/usr/src/uts/sun4v/Makefile.sun4v.shared index bf0b0d0daf..11b7c41d75 100644 --- a/usr/src/uts/sun4v/Makefile.sun4v.shared +++ b/usr/src/uts/sun4v/Makefile.sun4v.shared @@ -348,6 +348,7 @@ DRV_KMODS += vcc DRV_KMODS += vdc DRV_KMODS += vds DRV_KMODS += vldc +DRV_KMODS += vlds DRV_KMODS += vnet DRV_KMODS += vnex DRV_KMODS += vsw diff --git a/usr/src/uts/sun4v/io/ds.c b/usr/src/uts/sun4v/io/ds.c deleted file mode 100644 index 4e81501ff9..0000000000 --- a/usr/src/uts/sun4v/io/ds.c +++ /dev/null @@ -1,2962 +0,0 @@ -/* - * 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. - */ - -#pragma ident "%Z%%M% %I% %E% SMI" - -/* - * Domain Services Module - * - * The Domain Services (DS) module is responsible for communication - * with external service entities. It provides an API for clients to - * publish capabilities and handles the low level communication and - * version negotiation required to export those capabilities to any - * interested service entity. Once a capability has been successfully - * registered with a service entity, the DS module facilitates all - * data transfers between the service entity and the client providing - * that particular capability. - */ - -#include <sys/modctl.h> -#include <sys/ksynch.h> -#include <sys/taskq.h> -#include <sys/disp.h> -#include <sys/cmn_err.h> -#include <sys/note.h> - -#include <sys/mach_descrip.h> -#include <sys/mdesc.h> -#include <sys/ldc.h> - -#include <sys/ds.h> -#include <sys/ds_impl.h> - -/* - * All DS ports in the system - * - * The list of DS ports is read in from the MD when the DS module is - * initialized and is never modified. This eliminates the need for - * locking to access the port array itself. Access to the individual - * ports are synchronized at the port level. - */ -static ds_port_t ds_ports[DS_MAX_PORTS]; -static ds_portset_t ds_allports; /* all DS ports in the system */ - -/* - * Table of registered services - * - * Locking: Accesses to the table of services are synchronized using - * a RW lock. The reader lock must be held when looking up service - * information in the table. The writer lock must be held when any - * service information is being modified. - */ -static struct ds_svcs { - ds_svc_t **tbl; /* the table itself */ - krwlock_t rwlock; /* table lock */ - uint_t maxsvcs; /* size of the table */ - uint_t nsvcs; /* current number of items */ -} ds_svcs; - -/* initial size of the table */ -#define DS_MAXSVCS_INIT 32 - -/* - * Lock Usage - * - * ds_svcs.rwlock - * - * See comment just above definition of ds_svcs structure above. - * - * ds_port mutex - * - * Protects the elements of each port structure. Must be acquired for - * access to any of the elements. - * - * ds_log mutex - * - * See comment above definition of ds_log structure. - * - * Multiple lock requirements: - * - * Some code will need to access both a ds_svc_t structure and - * a ds_port_t. In that case, the acquisition order must be: - * - * ds_svcs.rwlock -> port lock - */ - -/* - * Taskq for internal task processing - */ -static taskq_t *ds_taskq; -static boolean_t ds_enabled; /* enable/disable taskq processing */ - -/* - * The actual required number of parallel threads is not expected - * to be very large. Use the maximum number of CPUs in the system - * as a rough upper bound. - */ -#define DS_MAX_TASKQ_THR NCPU -#define DS_DISPATCH(fn, arg) taskq_dispatch(ds_taskq, fn, arg, TQ_SLEEP) - -/* - * Retry count and delay for LDC reads and writes - */ -#define DS_DEFAULT_RETRIES 10000 /* number of times to retry */ -#define DS_DEFAULT_DELAY 1000 /* usecs to wait between retries */ - -static int ds_retries = DS_DEFAULT_RETRIES; -static clock_t ds_delay = DS_DEFAULT_DELAY; - -/* - * Supported versions of the DS message protocol - * - * The version array must be sorted in order from the highest - * supported version to the lowest. Support for a particular - * <major>.<minor> version implies all lower minor versions of - * that same major version are supported as well. - */ -static ds_ver_t ds_vers[] = { { 1, 0 } }; - -#define DS_NUM_VER (sizeof (ds_vers) / sizeof (ds_vers[0])) - -/* - * Results of checking version array with ds_vers_isvalid() - */ -typedef enum { - DS_VERS_OK, - DS_VERS_INCREASING_MAJOR_ERR, - DS_VERS_INCREASING_MINOR_ERR -} ds_vers_check_t; - -/* incoming message handling functions */ -typedef void (*ds_msg_handler_t)(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_init_req(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_init_ack(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_init_nack(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_reg_req(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_reg_ack(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_reg_nack(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_unreg_req(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_unreg_ack(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_unreg_nack(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_data(ds_port_t *port, caddr_t buf, size_t len); -static void ds_handle_nack(ds_port_t *port, caddr_t buf, size_t len); - -/* - * DS Message Handler Dispatch Table - * - * A table used to dispatch all incoming messages. This table - * contains handlers for all the fixed message types, as well as - * the the messages defined in the 1.0 version of the DS protocol. - */ -static const ds_msg_handler_t ds_msg_handlers[] = { - ds_handle_init_req, /* DS_INIT_REQ */ - ds_handle_init_ack, /* DS_INIT_ACK */ - ds_handle_init_nack, /* DS_INIT_NACK */ - ds_handle_reg_req, /* DS_REG_REQ */ - ds_handle_reg_ack, /* DS_REG_ACK */ - ds_handle_reg_nack, /* DS_REG_NACK */ - ds_handle_unreg_req, /* DS_UNREG */ - ds_handle_unreg_ack, /* DS_UNREG_ACK */ - ds_handle_unreg_nack, /* DS_UNREG_NACK */ - ds_handle_data, /* DS_DATA */ - ds_handle_nack /* DS_NACK */ -}; - -/* - * DS message log - * - * Locking: The message log is protected by a single mutex. This - * protects all fields in the log structure itself as well as - * everything in the entry structures on both the log and the - * free list. - */ -static struct log { - ds_log_entry_t *head; /* head of the log */ - ds_log_entry_t *freelist; /* head of the free list */ - size_t size; /* size of the log in bytes */ - uint32_t nentry; /* number of entries */ - kmutex_t lock; /* log lock */ -} ds_log; - -/* log soft limit */ -uint_t ds_log_sz = DS_LOG_DEFAULT_SZ; - -/* initial pool of log entry structures */ -static ds_log_entry_t ds_log_entry_pool[DS_LOG_NPOOL]; - -/* - * Error message features - */ -#define DS_EBUFSIZE 80 - -/* - * Debugging Features - */ -#ifdef DEBUG - -#define DS_DBG_FLAG_LDC 0x1 -#define DS_DBG_FLAG_LOG 0x2 -#define DS_DBG_FLAG_MSG 0x4 -#define DS_DBG_FLAG_ALL 0xf - -#define DS_DBG if (ds_debug) printf -#define DS_DBG_LDC if (ds_debug & DS_DBG_FLAG_LDC) printf -#define DS_DBG_LOG if (ds_debug & DS_DBG_FLAG_LOG) printf -#define DS_DBG_MSG if (ds_debug & DS_DBG_FLAG_MSG) printf -#define DS_DUMP_MSG(buf, len) ds_dump_msg(buf, len) - -uint_t ds_debug = 0; -static void ds_dump_msg(void *buf, size_t len); - -#else /* DEBUG */ - -#define DS_DBG _NOTE(CONSTCOND) if (0) printf -#define DS_DBG_LDC DS_DBG -#define DS_DBG_LOG DS_DBG -#define DS_DUMP_MSG(buf, len) - -#endif /* DEBUG */ - - -/* initialization functions */ -static void ds_init(void); -static void ds_fini(void); -static int ds_ports_init(void); -static int ds_ports_fini(void); -static int ds_ldc_init(ds_port_t *port); -static int ds_ldc_fini(ds_port_t *port); - -/* event processing functions */ -static uint_t ds_ldc_reconnect(ds_port_t *port); -static uint_t ds_ldc_cb(uint64_t event, caddr_t arg); -static void ds_dispatch_event(void *arg); -static int ds_recv_msg(ds_port_t *port, caddr_t msgp, size_t *sizep); -static void ds_handle_recv(void *arg); - -/* message sending functions */ -static int ds_send_msg(ds_port_t *port, caddr_t msg, size_t msglen); -static void ds_send_init_req(ds_port_t *port); -static int ds_send_reg_req(ds_svc_t *svc); -static int ds_send_unreg_req(ds_svc_t *svc); -static void ds_send_unreg_nack(ds_port_t *port, ds_svc_hdl_t bad_hdl); -static void ds_send_data_nack(ds_port_t *port, ds_svc_hdl_t bad_hdl); - -/* walker functions */ -typedef int (*svc_cb_t)(ds_svc_t *svc, void *arg); -static int ds_walk_svcs(svc_cb_t svc_cb, void *arg); -static int ds_svc_isfree(ds_svc_t *svc, void *arg); -static int ds_svc_ismatch(ds_svc_t *svc, void *arg); -static int ds_svc_free(ds_svc_t *svc, void *arg); -static int ds_svc_register(ds_svc_t *svc, void *arg); -static int ds_svc_unregister(ds_svc_t *svc, void *arg); -static int ds_svc_port_up(ds_svc_t *svc, void *arg); - -/* service utilities */ -static ds_svc_t *ds_alloc_svc(void); -static void ds_reset_svc(ds_svc_t *svc, ds_port_t *port); -static ds_svc_t *ds_get_svc(ds_svc_hdl_t hdl); - -/* port utilities */ -static int ds_port_add(md_t *mdp, mde_cookie_t port, mde_cookie_t chan); -static void ds_port_reset(ds_port_t *port); - -/* misc utilities */ -static ds_vers_check_t ds_vers_isvalid(ds_ver_t *vers, int nvers); -static char *ds_errno_to_str(int errno, char *ebuf); - -/* log functions */ -static void ds_log_init(void); -static void ds_log_fini(void); -static int ds_log_add_msg(int32_t dest, uint8_t *msg, size_t sz); -static int ds_log_remove(void); -static void ds_log_purge(void *arg); - - -static struct modlmisc modlmisc = { - &mod_miscops, - "Domain Services 1.8" -}; - -static struct modlinkage modlinkage = { - MODREV_1, - (void *)&modlmisc, - NULL -}; - -int -_init(void) -{ - int rv; - - /* - * Perform all internal setup before initializing - * the DS ports. This ensures that events can be - * processed as soon as the port comes up. - */ - ds_init(); - - /* force attach channel nexus */ - (void) i_ddi_attach_hw_nodes("cnex"); - - if ((rv = ds_ports_init()) != 0) { - cmn_err(CE_WARN, "Domain Services initialization failed"); - ds_fini(); - return (rv); - } - - if ((rv = mod_install(&modlinkage)) != 0) { - (void) ds_ports_fini(); - ds_fini(); - } - - return (rv); -} - -int -_info(struct modinfo *modinfop) -{ - return (mod_info(&modlinkage, modinfop)); -} - -int -_fini(void) -{ - int rv; - - if ((rv = mod_remove(&modlinkage)) == 0) { - (void) ds_ports_fini(); - ds_fini(); - } - - return (rv); -} - -static void -ds_init(void) -{ - int tblsz; - - /* - * Initialize table of registered service classes - */ - ds_svcs.maxsvcs = DS_MAXSVCS_INIT; - - tblsz = ds_svcs.maxsvcs * sizeof (ds_svc_t *); - ds_svcs.tbl = kmem_zalloc(tblsz, KM_SLEEP); - - rw_init(&ds_svcs.rwlock, NULL, RW_DRIVER, NULL); - - ds_svcs.nsvcs = 0; - - /* - * Initialize the message log. - */ - ds_log_init(); - - /* - * Create taskq for internal processing threads. This - * includes processing incoming request messages and - * sending out of band registration messages. - */ - ds_taskq = taskq_create("ds_taskq", 1, minclsyspri, 1, - DS_MAX_TASKQ_THR, TASKQ_PREPOPULATE | TASKQ_DYNAMIC); - - ds_enabled = B_TRUE; - - /* catch problems with the version array */ - ASSERT(ds_vers_isvalid(ds_vers, DS_NUM_VER) == DS_VERS_OK); -} - -static void -ds_fini(void) -{ - int idx; - - /* - * Flip the enabled switch to make sure that no - * incoming events get dispatched while things - * are being torn down. - */ - ds_enabled = B_FALSE; - - /* - * Destroy the taskq. - */ - taskq_destroy(ds_taskq); - - /* - * Destroy the message log. - */ - ds_log_fini(); - - /* - * Deallocate the table of registered services - */ - - /* clear out all entries */ - rw_enter(&ds_svcs.rwlock, RW_WRITER); - idx = ds_walk_svcs(ds_svc_free, NULL); - rw_exit(&ds_svcs.rwlock); - - /* should have gone through the whole table */ - ASSERT(idx == ds_svcs.maxsvcs); - - /* destroy the table itself */ - kmem_free(ds_svcs.tbl, ds_svcs.maxsvcs * sizeof (ds_svc_t *)); - rw_destroy(&ds_svcs.rwlock); - bzero(&ds_svcs, sizeof (ds_svcs)); -} - -/* - * Initialize the list of ports based on the MD. - */ -static int -ds_ports_init(void) -{ - int idx; - int rv = 0; - md_t *mdp; - int num_nodes; - int listsz; - mde_cookie_t rootnode; - mde_cookie_t dsnode; - mde_cookie_t *portp = NULL; - mde_cookie_t *chanp = NULL; - int nport; - int nchan; - ds_port_t *port; - - if ((mdp = md_get_handle()) == NULL) { - cmn_err(CE_WARN, "unable to initialize machine description"); - return (-1); - } - - num_nodes = md_node_count(mdp); - ASSERT(num_nodes > 0); - - listsz = num_nodes * sizeof (mde_cookie_t); - - /* allocate temporary storage for MD scans */ - portp = kmem_zalloc(listsz, KM_SLEEP); - chanp = kmem_zalloc(listsz, KM_SLEEP); - - rootnode = md_root_node(mdp); - ASSERT(rootnode != MDE_INVAL_ELEM_COOKIE); - - /* - * The root of the search for DS port nodes is the - * DS node. Perform a scan to find that node. - */ - nport = md_scan_dag(mdp, rootnode, md_find_name(mdp, DS_MD_ROOT_NAME), - md_find_name(mdp, "fwd"), portp); - - if (nport <= 0) { - DS_DBG("No '%s' node in MD\n", DS_MD_ROOT_NAME); - goto done; - } - - /* expecting only one DS node */ - if (nport != 1) { - DS_DBG("expected one '%s' node in the MD, found %d\n", - DS_MD_ROOT_NAME, nport); - } - - dsnode = portp[0]; - - /* find all the DS ports in the MD */ - nport = md_scan_dag(mdp, dsnode, md_find_name(mdp, DS_MD_PORT_NAME), - md_find_name(mdp, "fwd"), portp); - - if (nport <= 0) { - DS_DBG("No '%s' nodes in MD\n", DS_MD_PORT_NAME); - goto done; - } - - /* - * Initialize all the ports found in the MD. - */ - for (idx = 0; idx < nport; idx++) { - - /* get the channels for this port */ - nchan = md_scan_dag(mdp, portp[idx], - md_find_name(mdp, DS_MD_CHAN_NAME), - md_find_name(mdp, "fwd"), chanp); - - if (nchan <= 0) { - cmn_err(CE_NOTE, "No '%s' node for DS port", - DS_MD_CHAN_NAME); - rv = -1; - goto done; - } - - /* expecting only one channel */ - if (nchan != 1) { - DS_DBG("expected one '%s' node for DS port, found %d\n", - DS_MD_CHAN_NAME, nchan); - } - - if (ds_port_add(mdp, portp[idx], chanp[0]) != 0) { - rv = -1; - goto done; - } - } - - /* - * Initialize the LDC channel for each port. - */ - for (idx = 0; idx < DS_MAX_PORTS; idx++) { - - if (!DS_PORT_IN_SET(ds_allports, idx)) - continue; - - port = &ds_ports[idx]; - - mutex_enter(&port->lock); - - if (ds_ldc_init(port)) { - cmn_err(CE_WARN, "ds@%lx: ports_init: failed to " - "initialize LDC %ld", port->id, port->ldc.id); - } else { - DS_DBG("ds@%lx: ports_init: initialization complete\n", - port->id); - } - - mutex_exit(&port->lock); - } - - rv = 0; - -done: - if (rv != 0) - (void) ds_ports_fini(); - - kmem_free(portp, listsz); - kmem_free(chanp, listsz); - - (void) md_fini_handle(mdp); - - return (rv); -} - -static int -ds_ports_fini(void) -{ - int idx; - ds_port_t *port; - - /* - * Tear down each initialized port. - */ - for (idx = 0; idx < DS_MAX_PORTS; idx++) { - - if (!DS_PORT_IN_SET(ds_allports, idx)) - continue; - - port = &ds_ports[idx]; - - mutex_enter(&port->lock); - - if (port->state >= DS_PORT_LDC_INIT) { - /* shut down the LDC for this port */ - (void) ds_ldc_fini(port); - } - - port->state = DS_PORT_FREE; - - mutex_exit(&port->lock); - - /* clean up the port structure */ - mutex_destroy(&port->lock); - DS_PORTSET_DEL(ds_allports, idx); - } - - return (0); -} - -static int -ds_ldc_init(ds_port_t *port) -{ - int rv; - ldc_attr_t ldc_attr; - caddr_t cb_arg = (caddr_t)port; - char ebuf[DS_EBUFSIZE]; - - ASSERT(MUTEX_HELD(&port->lock)); - - DS_DBG("ds@%lx: ldc_init: ldc_id=%ld\n", port->id, port->ldc.id); - - ldc_attr.devclass = LDC_DEV_GENERIC; - ldc_attr.instance = 0; - ldc_attr.mode = LDC_MODE_RELIABLE; - ldc_attr.mtu = DS_STREAM_MTU; - - if ((rv = ldc_init(port->ldc.id, &ldc_attr, &port->ldc.hdl)) != 0) { - cmn_err(CE_WARN, "ds@%lx: ldc_init: %s", port->id, - ds_errno_to_str(rv, ebuf)); - goto done; - } - - /* register the LDC callback */ - if ((rv = ldc_reg_callback(port->ldc.hdl, ds_ldc_cb, cb_arg)) != 0) { - cmn_err(CE_WARN, "ds@%lx: ldc_reg_callback: %s", port->id, - ds_errno_to_str(rv, ebuf)); - goto done; - } - - if ((rv = ldc_open(port->ldc.hdl)) != 0) { - cmn_err(CE_WARN, "ds@%lx: ldc_open: %s", port->id, - ds_errno_to_str(rv, ebuf)); - goto done; - } - - (void) ldc_up(port->ldc.hdl); - - (void) ldc_status(port->ldc.hdl, &port->ldc.state); - - DS_DBG_LDC("ds@%lx: ldc_init: initial LDC state 0x%x\n", - port->id, port->ldc.state); - - port->state = DS_PORT_LDC_INIT; - - /* if port is up, send init message */ - if (port->ldc.state == LDC_UP) { - ds_send_init_req(port); - } - -done: - return (rv); -} - -static int -ds_ldc_fini(ds_port_t *port) -{ - int rv; - char ebuf[DS_EBUFSIZE]; - - ASSERT(port->state >= DS_PORT_LDC_INIT); - - DS_DBG("ds@%lx: ldc_fini: ldc_id=%ld\n", port->id, port->ldc.id); - - if ((rv = ldc_close(port->ldc.hdl)) != 0) { - cmn_err(CE_WARN, "ds@%lx: ldc_close: %s", port->id, - ds_errno_to_str(rv, ebuf)); - return (rv); - } - - if ((rv = ldc_unreg_callback(port->ldc.hdl)) != 0) { - cmn_err(CE_WARN, "ds@%lx: ldc_unreg_callback: %s", port->id, - ds_errno_to_str(rv, ebuf)); - return (rv); - } - - if ((rv = ldc_fini(port->ldc.hdl)) != 0) { - cmn_err(CE_WARN, "ds@%lx: ldc_fini: %s", port->id, - ds_errno_to_str(rv, ebuf)); - return (rv); - } - - return (rv); -} - -static uint_t -ds_ldc_reconnect(ds_port_t *port) -{ - ldc_status_t ldc_state; - int rv; - ldc_handle_t ldc_hdl; - int write_held; - char ebuf[DS_EBUFSIZE]; - - /* - * We can enter this code holding write lock via ds_handle_init_ack, - * ds_handle_reg_nack, ds_handle_unreg_req, and ds_handle_nack. We - * do not enter this code ever holding the read lock. - */ - write_held = RW_WRITE_HELD(&ds_svcs.rwlock); - if (!write_held) { - rw_enter(&ds_svcs.rwlock, RW_WRITER); - } - - mutex_enter(&port->lock); - - ldc_hdl = port->ldc.hdl; - - /* reset the port state */ - ds_port_reset(port); - (void) ldc_up(ldc_hdl); - - /* read status after bringing LDC up */ - if ((rv = ldc_status(ldc_hdl, &ldc_state)) != 0) { - cmn_err(CE_NOTE, "ds@%lx: ds_ldc_reconnect: ldc_status: %s", - port->id, ds_errno_to_str(rv, ebuf)); - } else { - port->ldc.state = ldc_state; - - /* - * If the channel is already up, initiate - * the handshake. - */ - if (ldc_state == LDC_UP) - ds_send_init_req(port); - - DS_DBG_LDC("ds@%lx: ds_ldc_reconnect: succeeded", port->id); - } - - mutex_exit(&port->lock); - if (!write_held) { - rw_exit(&ds_svcs.rwlock); - } - - return (rv); -} - -/* - * A DS event consists of a buffer on a port. - */ -typedef struct ds_event { - ds_port_t *port; - char *buf; - size_t buflen; -} ds_event_t; - -static uint_t -ds_ldc_cb(uint64_t event, caddr_t arg) -{ - ldc_status_t ldc_state; - int rv; - ds_port_t *port = (ds_port_t *)arg; - ldc_handle_t ldc_hdl; - char ebuf[DS_EBUFSIZE]; - - DS_DBG("ds@%lx: LDC event received: 0x%lx\n", port->id, event); - - if (!ds_enabled) { - DS_DBG("ds@%lx: callback handling is disabled\n", port->id); - return (LDC_SUCCESS); - } - - ldc_hdl = port->ldc.hdl; - - /* - * Check the LDC event. - */ - if (event & (LDC_EVT_DOWN | LDC_EVT_RESET)) { - - ASSERT((event & (LDC_EVT_UP | LDC_EVT_READ)) == 0); - - rv = ds_ldc_reconnect(port); - - return (rv); - } - - mutex_enter(&port->lock); - - if (event & LDC_EVT_UP) { - if ((rv = ldc_status(ldc_hdl, &ldc_state)) != 0) { - cmn_err(CE_NOTE, "ds@%lx: ds_ldc_cb: ldc_status: %s\n", - port->id, ds_errno_to_str(rv, ebuf)); - goto done; - } - port->ldc.state = ldc_state; - - /* initiate the handshake */ - ds_send_init_req(port); - } - - if (event & LDC_EVT_READ) { - /* dispatch a thread to handle the read event */ - if (DS_DISPATCH(ds_handle_recv, port) == NULL) { - cmn_err(CE_WARN, "error initiating event handler"); - } - } - - if (event & LDC_EVT_WRITE) { - cmn_err(CE_NOTE, "ds@%lx: LDC write event received, not" - " supported\n", port->id); - goto done; - } - - /* report any unknown LDC events */ - if (event & ~(LDC_EVT_UP | LDC_EVT_READ)) { - cmn_err(CE_NOTE, "ds@%lx: Unexpected LDC event received: " - "0x%lx\n", port->id, event); - } - -done: - mutex_exit(&port->lock); - - return (LDC_SUCCESS); -} - -/* - * Attempt to read a specified number of bytes from a particular LDC. - * Returns zero for success or the return code from the LDC read on - * failure. The actual number of bytes read from the LDC is returned - * in the size parameter. - */ -static int -ds_recv_msg(ds_port_t *port, caddr_t msgp, size_t *sizep) -{ - int rv = 0; - size_t bytes_req = *sizep; - size_t bytes_left = bytes_req; - size_t nbytes; - int retry_count = 0; - char ebuf[DS_EBUFSIZE]; - - *sizep = 0; - - DS_DBG_LDC("ds@%lx: attempting to read %ld bytes\n", port->id, - bytes_req); - - while (bytes_left > 0) { - - nbytes = bytes_left; - - if ((rv = ldc_read(port->ldc.hdl, msgp, &nbytes)) != 0) { - if (rv == ECONNRESET) { - break; - } else if (rv != EAGAIN) { - cmn_err(CE_NOTE, "ds@%lx: ds_recv_msg: %s", - port->id, ds_errno_to_str(rv, ebuf)); - break; - } - } else { - if (nbytes != 0) { - DS_DBG_LDC("ds@%lx: read %ld bytes, %d " - "retries\n", port->id, nbytes, retry_count); - - *sizep += nbytes; - msgp += nbytes; - bytes_left -= nbytes; - - /* reset counter on a successful read */ - retry_count = 0; - continue; - } - - /* - * No data was read. Check if this is the - * first attempt. If so, just return since - * nothing has been read yet. - */ - if (bytes_left == bytes_req) { - DS_DBG_LDC("ds@%lx: read zero bytes, no data " - "available\n", port->id); - break; - } - } - - /* - * A retry is necessary because the read returned - * EAGAIN, or a zero length read occurred after - * reading a partial message. - */ - if (retry_count++ >= ds_retries) { - DS_DBG_LDC("ds@%lx: timed out waiting for " - "message\n", port->id); - break; - } - - drv_usecwait(ds_delay); - } - - return (rv); -} - -static void -ds_handle_recv(void *arg) -{ - ds_port_t *port = (ds_port_t *)arg; - char *hbuf; - size_t msglen; - size_t read_size; - boolean_t hasdata; - ds_hdr_t hdr; - uint8_t *msg; - char *currp; - int rv; - ldc_handle_t ldc_hdl; - ds_event_t *devent; - - DS_DBG("ds@%lx: ds_handle_recv...\n", port->id); - - ldc_hdl = port->ldc.hdl; - - mutex_enter(&port->lock); - - /* - * Read messages from the channel until there are none - * pending. Valid messages are dispatched to be handled - * by a separate thread while any malformed messages are - * dropped. - */ - while ((rv = ldc_chkq(ldc_hdl, &hasdata)) == 0 && hasdata) { - - DS_DBG("ds@%lx: reading next message\n", port->id); - - /* - * Read in the next message. - */ - hbuf = (char *)&hdr; - bzero(hbuf, DS_HDR_SZ); - read_size = DS_HDR_SZ; - currp = hbuf; - - /* read in the message header */ - if ((rv = ds_recv_msg(port, currp, &read_size)) != 0) { - break; - } - - if (read_size < DS_HDR_SZ) { - /* - * A zero length read is a valid signal that - * there is no data left on the channel. - */ - if (read_size != 0) { - cmn_err(CE_NOTE, "ds@%lx: invalid message " - "length, received %ld bytes, expected %ld", - port->id, read_size, DS_HDR_SZ); - } - continue; - } - - /* get payload size and allocate a buffer */ - read_size = ((ds_hdr_t *)hbuf)->payload_len; - msglen = DS_HDR_SZ + read_size; - msg = kmem_zalloc(msglen, KM_SLEEP); - - /* move message header into buffer */ - bcopy(hbuf, msg, DS_HDR_SZ); - currp = (char *)(msg) + DS_HDR_SZ; - - /* read in the message body */ - if ((rv = ds_recv_msg(port, currp, &read_size)) != 0) { - kmem_free(msg, msglen); - break; - } - - /* validate the size of the message */ - if ((DS_HDR_SZ + read_size) != msglen) { - cmn_err(CE_NOTE, "ds@%lx: invalid message length, " - "received %ld bytes, expected %ld", port->id, - (DS_HDR_SZ + read_size), msglen); - kmem_free(msg, msglen); - continue; - } - - DS_DUMP_MSG(msg, msglen); - - /* - * Send the message for processing, and store it - * in the log. The memory is deallocated only when - * the message is removed from the log. - */ - - devent = kmem_zalloc(sizeof (ds_event_t), KM_SLEEP); - devent->port = port; - devent->buf = (char *)msg; - devent->buflen = msglen; - - /* log the message */ - (void) ds_log_add_msg(DS_LOG_IN(port->id), msg, msglen); - - /* send the message off to get processed in a new thread */ - if (DS_DISPATCH(ds_dispatch_event, devent) == NULL) { - cmn_err(CE_WARN, "error initiating event handler"); - kmem_free(devent, sizeof (ds_event_t)); - continue; - } - - } - - mutex_exit(&port->lock); - - if (rv == ECONNRESET) { - (void) ds_ldc_reconnect(port); - } -} - -static void -ds_dispatch_event(void *arg) -{ - ds_event_t *event = (ds_event_t *)arg; - ds_hdr_t *hdr; - ds_port_t *port; - - port = event->port; - - hdr = (ds_hdr_t *)event->buf; - - if (!DS_MSG_TYPE_VALID(hdr->msg_type)) { - cmn_err(CE_NOTE, "ds@%lx: dispatch_event: invalid msg " - "type (%d)", port->id, hdr->msg_type); - goto done; - } - - DS_DBG("ds@%lx: dispatch_event: msg_type=%d\n", port->id, - hdr->msg_type); - - (*ds_msg_handlers[hdr->msg_type])(port, event->buf, event->buflen); - -done: - kmem_free(event->buf, event->buflen); - kmem_free(event, sizeof (ds_event_t)); -} - -/* - * Version negotiation is always initiated by the guest. Any - * attempt by a remote party to initiate the handshake gets - * nack'd with a major number equal to zero. This indicates - * that no version is supported since an init request is not - * expected. - */ -static void -ds_handle_init_req(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_hdr_t *hdr; - ds_init_nack_t *nack; - char *msg; - size_t msglen; - ds_init_req_t *req; - size_t explen = DS_MSG_LEN(ds_init_req_t); - - req = (ds_init_req_t *)(buf + DS_HDR_SZ); - - /* sanity check the incoming message */ - if (len != explen) { - cmn_err(CE_NOTE, "ds@%lx: <init_req: invalid message length " - "(%ld), expected %ld", port->id, len, explen); - } else { - DS_DBG("ds@%lx: <init_req: ver=%d.%d\n", port->id, - req->major_vers, req->minor_vers); - } - - DS_DBG("ds@%lx: init_nack>: major=0\n", port->id); - - msglen = DS_MSG_LEN(ds_init_nack_t); - msg = kmem_zalloc(msglen, KM_SLEEP); - - hdr = (ds_hdr_t *)msg; - hdr->msg_type = DS_INIT_NACK; - hdr->payload_len = sizeof (ds_init_nack_t); - - nack = (ds_init_nack_t *)(msg + DS_HDR_SZ); - nack->major_vers = 0; - - /* send message */ - mutex_enter(&port->lock); - (void) ds_send_msg(port, msg, msglen); - mutex_exit(&port->lock); - - kmem_free(msg, msglen); -} - -static void -ds_handle_init_ack(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_init_ack_t *ack; - ds_ver_t *ver; - size_t explen = DS_MSG_LEN(ds_init_ack_t); - - /* sanity check the incoming message */ - if (len != explen) { - cmn_err(CE_NOTE, "ds@%lx: <init_ack: invalid message length " - "(%ld), expected %ld", port->id, len, explen); - return; - } - - ack = (ds_init_ack_t *)(buf + DS_HDR_SZ); - - mutex_enter(&port->lock); - - if (port->state != DS_PORT_INIT_REQ) { - cmn_err(CE_NOTE, "ds@%lx: <init_ack: invalid state for msg " - "(%d)", port->id, port->state); - mutex_exit(&port->lock); - return; - } - - ver = &(ds_vers[port->ver_idx]); - - DS_DBG("ds@%lx: <init_ack: req=v%d.%d, ack=v%d.%d\n", port->id, - ver->major, ver->minor, ver->major, ack->minor_vers); - - /* agreed upon a major version */ - port->ver.major = ver->major; - - /* - * If the returned minor version is larger than - * the requested minor version, use the lower of - * the two, i.e. the requested version. - */ - if (ack->minor_vers >= ver->minor) { - /* - * Use the minor version specified in the - * original request. - */ - port->ver.minor = ver->minor; - } else { - /* - * Use the lower minor version returned in - * the ack. By definition, all lower minor - * versions must be supported. - */ - port->ver.minor = ack->minor_vers; - } - - port->state = DS_PORT_READY; - - DS_DBG("ds@%lx: <init_ack: port ready v%d.%d\n", port->id, - port->ver.major, port->ver.minor); - - mutex_exit(&port->lock); - - /* - * The port came up, so update all the services - * with this information. Follow that up with an - * attempt to register any service that is not - * already registered. - */ - rw_enter(&ds_svcs.rwlock, RW_WRITER); - - (void) ds_walk_svcs(ds_svc_port_up, port); - (void) ds_walk_svcs(ds_svc_register, NULL); - - rw_exit(&ds_svcs.rwlock); -} - -static void -ds_handle_init_nack(ds_port_t *port, caddr_t buf, size_t len) -{ - int idx; - ds_init_nack_t *nack; - ds_ver_t *ver; - size_t explen = DS_MSG_LEN(ds_init_nack_t); - - /* sanity check the incoming message */ - if (len != explen) { - cmn_err(CE_NOTE, "ds@%lx: <init_nack: invalid message length " - "(%ld), expected %ld", port->id, len, explen); - return; - } - - nack = (ds_init_nack_t *)(buf + DS_HDR_SZ); - - mutex_enter(&port->lock); - - if (port->state != DS_PORT_INIT_REQ) { - cmn_err(CE_NOTE, "ds@%lx: <init_nack: invalid state for msg " - "(%d)", port->id, port->state); - mutex_exit(&port->lock); - return; - } - - ver = &(ds_vers[port->ver_idx]); - - DS_DBG("ds@%lx: <init_nack: req=v%d.%d, nack=v%d.x\n", port->id, - ver->major, ver->minor, nack->major_vers); - - if (nack->major_vers == 0) { - /* no supported protocol version */ - cmn_err(CE_NOTE, "ds@%lx: <init_nack: DS not supported", - port->id); - mutex_exit(&port->lock); - return; - } - - /* - * Walk the version list, looking for a major version - * that is as close to the requested major version as - * possible. - */ - for (idx = port->ver_idx; idx < DS_NUM_VER; idx++) { - if (ds_vers[idx].major <= nack->major_vers) { - /* found a version to try */ - goto done; - } - } - - if (idx == DS_NUM_VER) { - /* no supported version */ - cmn_err(CE_NOTE, "ds@%lx: <init_nack: DS v%d.x not supported", - port->id, nack->major_vers); - - mutex_exit(&port->lock); - return; - } - -done: - /* start the handshake again */ - port->ver_idx = idx; - port->state = DS_PORT_LDC_INIT; - - ds_send_init_req(port); - - mutex_exit(&port->lock); -} - -static void -ds_handle_reg_req(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_hdr_t *hdr; - ds_reg_req_t *req; - ds_reg_nack_t *nack; - char *msg; - size_t msglen; - size_t explen = DS_MSG_LEN(ds_reg_req_t); - - /* the request information */ - req = (ds_reg_req_t *)(buf + DS_HDR_SZ); - - /* sanity check the incoming message */ - if (len < explen) { - cmn_err(CE_NOTE, "ds@%lx: <reg_req: invalid message length " - "(%ld), expected at least %ld", port->id, len, explen); - } else { - DS_DBG("ds@%lx: <reg_req: id='%s', ver=%d.%d, hdl=0x%09lx\n", - port->id, req->svc_id, req->major_vers, req->minor_vers, - req->svc_handle); - } - - DS_DBG("ds@%lx: reg_nack>: major=0\n", port->id); - - msglen = DS_MSG_LEN(ds_reg_nack_t); - msg = kmem_zalloc(msglen, KM_SLEEP); - - hdr = (ds_hdr_t *)msg; - hdr->msg_type = DS_REG_NACK; - hdr->payload_len = sizeof (ds_reg_nack_t); - - nack = (ds_reg_nack_t *)(msg + DS_HDR_SZ); - nack->svc_handle = req->svc_handle; - nack->result = DS_REG_VER_NACK; - nack->major_vers = 0; - - /* send message */ - mutex_enter(&port->lock); - (void) ds_send_msg(port, msg, msglen); - mutex_exit(&port->lock); - - kmem_free(msg, msglen); -} - -static void -ds_handle_reg_ack(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_reg_ack_t *ack; - ds_ver_t *ver; - ds_ver_t tmpver; - ds_svc_t *svc; - size_t explen = DS_MSG_LEN(ds_reg_ack_t); - - /* sanity check the incoming message */ - if (len != explen) { - cmn_err(CE_NOTE, "ds@%lx: <reg_ack: invalid message length " - "(%ld), expected %ld", port->id, len, explen); - return; - } - - ack = (ds_reg_ack_t *)(buf + DS_HDR_SZ); - - rw_enter(&ds_svcs.rwlock, RW_WRITER); - - /* lookup appropriate client */ - if ((svc = ds_get_svc(ack->svc_handle)) == NULL) { - cmn_err(CE_NOTE, "ds@%lx: <reg_ack: invalid handle 0x%lx", - port->id, ack->svc_handle); - goto done; - } - - /* make sure the message makes sense */ - if (svc->state != DS_SVC_REG_PENDING) { - cmn_err(CE_NOTE, "ds@%lx: <reg_ack: invalid state for message " - "(%d)", port->id, svc->state); - goto done; - } - - ver = &(svc->cap.vers[svc->ver_idx]); - - DS_DBG("ds@%lx: <reg_ack: hdl=0x%09lx, ack=v%d.%d\n", port->id, - ack->svc_handle, ver->major, ack->minor_vers); - - /* major version has been agreed upon */ - svc->ver.major = ver->major; - - if (ack->minor_vers >= ver->minor) { - /* - * Use the minor version specified in the - * original request. - */ - svc->ver.minor = ver->minor; - } else { - /* - * Use the lower minor version returned in - * the ack. By definition, all lower minor - * versions must be supported. - */ - svc->ver.minor = ack->minor_vers; - } - - svc->state = DS_SVC_ACTIVE; - - DS_DBG("ds@%lx: <reg_ack: %s v%d.%d ready, hdl=0x%09lx\n", port->id, - svc->cap.svc_id, svc->ver.major, svc->ver.minor, svc->hdl); - - /* notify the client that registration is complete */ - if (svc->ops.ds_reg_cb) { - /* - * Use a temporary version structure so that - * the copy in the svc structure cannot be - * modified by the client. - */ - tmpver.major = svc->ver.major; - tmpver.minor = svc->ver.minor; - - (*svc->ops.ds_reg_cb)(svc->ops.cb_arg, &tmpver, svc->hdl); - } - -done: - rw_exit(&ds_svcs.rwlock); -} - -static void -ds_handle_reg_nack(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_reg_nack_t *nack; - ds_svc_t *svc; - int idx; - boolean_t reset_svc = B_FALSE; - size_t explen = DS_MSG_LEN(ds_reg_nack_t); - - /* sanity check the incoming message */ - if (len != explen) { - cmn_err(CE_NOTE, "ds@%lx: <reg_nack: invalid message length " - "(%ld), expected %ld", port->id, len, explen); - return; - } - - nack = (ds_reg_nack_t *)(buf + DS_HDR_SZ); - - rw_enter(&ds_svcs.rwlock, RW_WRITER); - - /* lookup appropriate client */ - if ((svc = ds_get_svc(nack->svc_handle)) == NULL) { - cmn_err(CE_NOTE, "ds@%lx: <reg_nack: invalid handle 0x%lx", - port->id, nack->svc_handle); - goto done; - } - - /* make sure the message makes sense */ - if (svc->state != DS_SVC_REG_PENDING) { - cmn_err(CE_NOTE, "ds@%lx: <reg_nack: invalid state for message " - "(%d)", port->id, svc->state); - goto done; - } - - if (nack->result == DS_REG_DUP) { - cmn_err(CE_NOTE, "ds@%lx: <reg_nack: duplicate registration " - "for %s", port->id, svc->cap.svc_id); - reset_svc = B_TRUE; - goto done; - } - - /* - * A major version of zero indicates that the - * service is not supported at all. - */ - if (nack->major_vers == 0) { - DS_DBG("ds@%lx: <reg_nack: %s not supported\n", port->id, - svc->cap.svc_id); - reset_svc = B_TRUE; - goto done; - } - - DS_DBG("ds@%lx: <reg_nack: hdl=0x%09lx, nack=%d.x\n", port->id, - nack->svc_handle, nack->major_vers); - - /* - * Walk the version list for the service, looking for - * a major version that is as close to the requested - * major version as possible. - */ - for (idx = svc->ver_idx; idx < svc->cap.nvers; idx++) { - if (svc->cap.vers[idx].major <= nack->major_vers) { - /* found a version to try */ - break; - } - } - - if (idx == svc->cap.nvers) { - /* no supported version */ - DS_DBG("ds@%lx: <reg_nack: %s v%d.x not supported\n", - port->id, svc->cap.svc_id, nack->major_vers); - reset_svc = B_TRUE; - goto done; - } - - /* start the handshake again */ - svc->state = DS_SVC_INACTIVE; - svc->ver_idx = idx; - - (void) ds_svc_register(svc, NULL); - -done: - if (reset_svc) - ds_reset_svc(svc, port); - - rw_exit(&ds_svcs.rwlock); -} - -static void -ds_handle_unreg_req(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_hdr_t *hdr; - ds_unreg_req_t *req; - ds_unreg_ack_t *ack; - ds_svc_t *svc; - char *msg; - size_t msglen; - size_t explen = DS_MSG_LEN(ds_unreg_req_t); - - /* sanity check the incoming message */ - if (len != explen) { - cmn_err(CE_NOTE, "ds@%lx: <unreg_req: invalid message length " - "(%ld), expected %ld", port->id, len, explen); - return; - } - - /* the request information */ - req = (ds_unreg_req_t *)(buf + DS_HDR_SZ); - - rw_enter(&ds_svcs.rwlock, RW_WRITER); - - /* lookup appropriate client */ - if ((svc = ds_get_svc(req->svc_handle)) == NULL) { - cmn_err(CE_NOTE, "ds@%lx: <unreg_req: invalid handle " - "0x%lx", port->id, req->svc_handle); - ds_send_unreg_nack(port, req->svc_handle); - goto done; - } - - /* unregister the service */ - (void) ds_svc_unregister(svc, svc->port); - - DS_DBG("ds@%lx: unreg_ack>: hdl=0x%09lx\n", port->id, req->svc_handle); - - msglen = DS_HDR_SZ + sizeof (ds_unreg_ack_t); - msg = kmem_zalloc(msglen, KM_SLEEP); - - hdr = (ds_hdr_t *)msg; - hdr->msg_type = DS_UNREG_ACK; - hdr->payload_len = sizeof (ds_unreg_ack_t); - - ack = (ds_unreg_ack_t *)(msg + DS_HDR_SZ); - ack->svc_handle = req->svc_handle; - - /* send message */ - mutex_enter(&port->lock); - (void) ds_send_msg(port, msg, msglen); - mutex_exit(&port->lock); - - kmem_free(msg, msglen); - -done: - rw_exit(&ds_svcs.rwlock); -} - -static void -ds_handle_unreg_ack(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_unreg_ack_t *ack; - size_t explen = DS_MSG_LEN(ds_unreg_ack_t); - - /* sanity check the incoming message */ - if (len != explen) { - cmn_err(CE_NOTE, "ds@%lx: <unreg_ack: invalid message length " - "(%ld), expected %ld", port->id, len, explen); - return; - } - - ack = (ds_unreg_ack_t *)(buf + DS_HDR_SZ); - - DS_DBG("ds@%lx: <unreg_ack: hdl=0x%09lx\n", port->id, ack->svc_handle); - - rw_enter(&ds_svcs.rwlock, RW_WRITER); - - /* - * Since the unregister request was initiated locally, - * the service structure has already been torn down. - * Just perform a sanity check to make sure the message - * is appropriate. - */ - if (ds_get_svc(ack->svc_handle) != NULL) { - cmn_err(CE_NOTE, "ds@%lx: <unreg_ack: handle 0x%lx still " - "in use", port->id, ack->svc_handle); - } - - rw_exit(&ds_svcs.rwlock); -} - -static void -ds_handle_unreg_nack(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_unreg_nack_t *nack; - size_t explen = DS_MSG_LEN(ds_unreg_nack_t); - - /* sanity check the incoming message */ - if (len != explen) { - cmn_err(CE_NOTE, "ds@%lx: <unreg_nack: invalid message length " - "(%ld), expected %ld", port->id, len, explen); - return; - } - - nack = (ds_unreg_nack_t *)(buf + DS_HDR_SZ); - - DS_DBG("ds@%lx: <unreg_nack: hdl=0x%09lx\n", port->id, - nack->svc_handle); - - rw_enter(&ds_svcs.rwlock, RW_WRITER); - - /* - * Since the unregister request was initiated locally, - * the service structure has already been torn down. - * Just perform a sanity check to make sure the message - * is appropriate. - */ - if (ds_get_svc(nack->svc_handle) != NULL) { - cmn_err(CE_NOTE, "ds@%lx: <unreg_nack: handle 0x%lx still " - "in use", port->id, nack->svc_handle); - } - - rw_exit(&ds_svcs.rwlock); -} - -static void -ds_handle_data(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_data_handle_t *data; - ds_svc_t *svc; - char *msg; - int msgsz; - int hdrsz; - size_t explen = DS_MSG_LEN(ds_data_handle_t); - - /* sanity check the incoming message */ - if (len < explen) { - cmn_err(CE_NOTE, "ds@%lx: <data: invalid message length " - "(%ld), expected at least %ld", port->id, len, explen); - return; - } - - data = (ds_data_handle_t *)(buf + DS_HDR_SZ); - - hdrsz = DS_HDR_SZ + sizeof (ds_data_handle_t); - msgsz = len - hdrsz; - - /* strip off the header for the client */ - msg = (msgsz) ? (buf + hdrsz) : NULL; - - rw_enter(&ds_svcs.rwlock, RW_READER); - - /* lookup appropriate client */ - if ((svc = ds_get_svc(data->svc_handle)) == NULL) { - cmn_err(CE_NOTE, "ds@%lx: <data: invalid handle 0x%lx", - port->id, data->svc_handle); - rw_exit(&ds_svcs.rwlock); - ds_send_data_nack(port, data->svc_handle); - return; - } - - rw_exit(&ds_svcs.rwlock); - - DS_DBG("ds@%lx: <data: client=%s hdl=0x%09lx\n", port->id, - (svc->cap.svc_id) ? svc->cap.svc_id : "NULL", svc->hdl); - - /* dispatch this message to the client */ - (*svc->ops.ds_data_cb)(svc->ops.cb_arg, msg, msgsz); -} - -static void -ds_handle_nack(ds_port_t *port, caddr_t buf, size_t len) -{ - ds_svc_t *svc; - ds_data_nack_t *nack; - size_t explen = DS_MSG_LEN(ds_data_nack_t); - - /* sanity check the incoming message */ - if (len != explen) { - cmn_err(CE_NOTE, "ds@%lx: <data_nack: invalid message length " - "(%ld), expected %ld", port->id, len, explen); - return; - } - - nack = (ds_data_nack_t *)(buf + DS_HDR_SZ); - - DS_DBG("ds@%lx: data_nack: hdl=0x%09lx, result=0x%lx\n", port->id, - nack->svc_handle, nack->result); - - if (nack->result == DS_INV_HDL) { - - rw_enter(&ds_svcs.rwlock, RW_WRITER); - - if ((svc = ds_get_svc(nack->svc_handle)) == NULL) { - rw_exit(&ds_svcs.rwlock); - return; - } - - cmn_err(CE_NOTE, "ds@%lx: <data_nack: handle 0x%lx reported " - "as invalid", port->id, nack->svc_handle); - - (void) ds_svc_unregister(svc, svc->port); - - rw_exit(&ds_svcs.rwlock); - } -} - -static int -ds_send_msg(ds_port_t *port, caddr_t msg, size_t msglen) -{ - int rv; - caddr_t currp = msg; - size_t amt_left = msglen; - int loopcnt = 0; - char ebuf[DS_EBUFSIZE]; - - DS_DUMP_MSG(msg, msglen); - - (void) ds_log_add_msg(DS_LOG_OUT(port->id), (uint8_t *)msg, msglen); - - /* - * ensure that no other messages can be sent on this port in case - * the write doesn't get sent with one write to guarantee that the - * message doesn't become fragmented. - */ - ASSERT(MUTEX_HELD(&port->lock)); - - /* send the message */ - do { - if ((rv = ldc_write(port->ldc.hdl, currp, &msglen)) != 0) { - if (rv == ECONNRESET) { - mutex_exit(&port->lock); - (void) ds_ldc_reconnect(port); - mutex_enter(&port->lock); - return (rv); - } else if ((rv == EWOULDBLOCK) && - (loopcnt++ < ds_retries)) { - drv_usecwait(ds_delay); - } else { - cmn_err(CE_WARN, "ds@%lx: ldc_write: %s", - port->id, ds_errno_to_str(rv, ebuf)); - return (rv); - } - } else { - amt_left -= msglen; - currp += msglen; - msglen = amt_left; - loopcnt = 0; - } - } while (amt_left > 0); - - return (rv); -} - -static void -ds_send_init_req(ds_port_t *port) -{ - ds_hdr_t *hdr; - ds_init_req_t *init_req; - size_t msglen; - ds_ver_t *vers = &ds_vers[port->ver_idx]; - - ASSERT(MUTEX_HELD(&port->lock)); - - if (port->state != DS_PORT_LDC_INIT) { - cmn_err(CE_NOTE, "ds@%lx: init_req>: invalid port state (%d)", - port->id, port->state); - return; - } - - DS_DBG("ds@%lx: init_req>: req=v%d.%d\n", port->id, vers->major, - vers->minor); - - msglen = DS_HDR_SZ + sizeof (ds_init_req_t); - hdr = kmem_zalloc(msglen, KM_SLEEP); - - hdr->msg_type = DS_INIT_REQ; - hdr->payload_len = sizeof (ds_init_req_t); - - init_req = (ds_init_req_t *)((caddr_t)hdr + DS_HDR_SZ); - init_req->major_vers = vers->major; - init_req->minor_vers = vers->minor; - - /* send the message */ - if (ds_send_msg(port, (caddr_t)hdr, msglen) == 0) { - port->state = DS_PORT_INIT_REQ; - } - - kmem_free(hdr, msglen); -} - -static int -ds_send_reg_req(ds_svc_t *svc) -{ - int rv = 0; - ds_port_t *port = svc->port; - ds_ver_t *ver; - ds_hdr_t *hdr; - caddr_t msg; - size_t msglen; - ds_reg_req_t *req; - size_t idlen; - - /* assumes some checking has already occurred */ - ASSERT(svc->state == DS_SVC_INACTIVE); - - mutex_enter(&port->lock); - - /* check on the LDC to Zeus */ - if (port->ldc.state != LDC_UP) { - /* can not send message */ - DS_DBG("ds@%lx: reg_req>: channel %ld is not up\n", port->id, - port->ldc.id); - mutex_exit(&port->lock); - return (-1); - } - - /* make sure port is ready */ - if (port->state != DS_PORT_READY) { - /* can not send message */ - DS_DBG("ds@%lx: reg_req>: port is not ready\n", port->id); - mutex_exit(&port->lock); - return (-1); - } - - mutex_exit(&port->lock); - - /* allocate the message buffer */ - idlen = strlen(svc->cap.svc_id); - msglen = DS_HDR_SZ + sizeof (ds_reg_req_t) + idlen; - msg = kmem_zalloc(msglen, KM_SLEEP); - - /* copy in the header data */ - hdr = (ds_hdr_t *)msg; - hdr->msg_type = DS_REG_REQ; - hdr->payload_len = sizeof (ds_reg_req_t) + idlen; - - req = (ds_reg_req_t *)(msg + DS_HDR_SZ); - req->svc_handle = svc->hdl; - ver = &(svc->cap.vers[svc->ver_idx]); - req->major_vers = ver->major; - req->minor_vers = ver->minor; - - /* copy in the service id */ - bcopy(svc->cap.svc_id, req->svc_id, idlen + 1); - - /* send the message */ - DS_DBG("ds@%lx: reg_req>: id='%s', ver=%d.%d, hdl=0x%09lx\n", port->id, - svc->cap.svc_id, ver->major, ver->minor, svc->hdl); - - mutex_enter(&port->lock); - if (ds_send_msg(port, msg, msglen) != 0) { - rv = -1; - } else { - svc->state = DS_SVC_REG_PENDING; - } - mutex_exit(&port->lock); - - kmem_free(msg, msglen); - return (rv); -} - -static int -ds_send_unreg_req(ds_svc_t *svc) -{ - int rv = 0; - caddr_t msg; - size_t msglen; - ds_hdr_t *hdr; - ds_unreg_req_t *req; - ds_port_t *port = svc->port; - - if (port == NULL) { - DS_DBG("send_unreg_req: service '%s' not associated with " - "a port\n", svc->cap.svc_id); - return (-1); - } - - mutex_enter(&port->lock); - - /* check on the LDC to Zeus */ - if (port->ldc.state != LDC_UP) { - /* can not send message */ - cmn_err(CE_NOTE, "ds@%lx: unreg_req>: channel %ld is not up\n", - port->id, port->ldc.id); - mutex_exit(&port->lock); - return (-1); - } - - /* make sure port is ready */ - if (port->state != DS_PORT_READY) { - /* can not send message */ - cmn_err(CE_NOTE, "ds@%lx: unreg_req>: port is not ready\n", - port->id); - mutex_exit(&port->lock); - return (-1); - } - - mutex_exit(&port->lock); - - msglen = DS_HDR_SZ + sizeof (ds_unreg_req_t); - msg = kmem_zalloc(msglen, KM_SLEEP); - - /* copy in the header data */ - hdr = (ds_hdr_t *)msg; - hdr->msg_type = DS_UNREG; - hdr->payload_len = sizeof (ds_unreg_req_t); - - req = (ds_unreg_req_t *)(msg + DS_HDR_SZ); - req->svc_handle = svc->hdl; - - /* send the message */ - DS_DBG("ds@%lx: unreg_req>: id='%s', hdl=0x%09lx\n", port->id, - (svc->cap.svc_id) ? svc->cap.svc_id : "NULL", svc->hdl); - - mutex_enter(&port->lock); - - if (ds_send_msg(port, msg, msglen) != 0) - rv = -1; - - mutex_exit(&port->lock); - - kmem_free(msg, msglen); - return (rv); -} - -static void -ds_send_unreg_nack(ds_port_t *port, ds_svc_hdl_t bad_hdl) -{ - caddr_t msg; - size_t msglen; - ds_hdr_t *hdr; - ds_unreg_nack_t *nack; - - mutex_enter(&port->lock); - - /* check on the LDC to Zeus */ - if (port->ldc.state != LDC_UP) { - /* can not send message */ - cmn_err(CE_NOTE, "ds@%lx: unreg_nack>: channel %ld is not up", - port->id, port->ldc.id); - mutex_exit(&port->lock); - return; - } - - /* make sure port is ready */ - if (port->state != DS_PORT_READY) { - /* can not send message */ - cmn_err(CE_NOTE, "ds@%lx: unreg_nack>: port is not ready", - port->id); - mutex_exit(&port->lock); - return; - } - - mutex_exit(&port->lock); - - msglen = DS_HDR_SZ + sizeof (ds_unreg_nack_t); - msg = kmem_zalloc(msglen, KM_SLEEP); - - /* copy in the header data */ - hdr = (ds_hdr_t *)msg; - hdr->msg_type = DS_UNREG_NACK; - hdr->payload_len = sizeof (ds_unreg_nack_t); - - nack = (ds_unreg_nack_t *)(msg + DS_HDR_SZ); - nack->svc_handle = bad_hdl; - - /* send the message */ - DS_DBG("ds@%lx: unreg_nack>: hdl=0x%09lx\n", port->id, bad_hdl); - - mutex_enter(&port->lock); - (void) ds_send_msg(port, msg, msglen); - mutex_exit(&port->lock); - - kmem_free(msg, msglen); -} - -static void -ds_send_data_nack(ds_port_t *port, ds_svc_hdl_t bad_hdl) -{ - caddr_t msg; - size_t msglen; - ds_hdr_t *hdr; - ds_data_nack_t *nack; - - mutex_enter(&port->lock); - - /* check on the LDC to Zeus */ - if (port->ldc.state != LDC_UP) { - /* can not send message */ - cmn_err(CE_NOTE, "ds@%lx: data_nack>: channel %ld is not up", - port->id, port->ldc.id); - mutex_exit(&port->lock); - return; - } - - /* make sure port is ready */ - if (port->state != DS_PORT_READY) { - /* can not send message */ - cmn_err(CE_NOTE, "ds@%lx: data_nack>: port is not ready", - port->id); - mutex_exit(&port->lock); - return; - } - - mutex_exit(&port->lock); - - msglen = DS_HDR_SZ + sizeof (ds_data_nack_t); - msg = kmem_zalloc(msglen, KM_SLEEP); - - /* copy in the header data */ - hdr = (ds_hdr_t *)msg; - hdr->msg_type = DS_NACK; - hdr->payload_len = sizeof (ds_data_nack_t); - - nack = (ds_data_nack_t *)(msg + DS_HDR_SZ); - nack->svc_handle = bad_hdl; - nack->result = DS_INV_HDL; - - /* send the message */ - DS_DBG("ds@%lx: data_nack>: hdl=0x%09lx\n", port->id, bad_hdl); - - mutex_enter(&port->lock); - (void) ds_send_msg(port, msg, msglen); - mutex_exit(&port->lock); - - kmem_free(msg, msglen); -} - -#ifdef DEBUG - -#define BYTESPERLINE 8 -#define LINEWIDTH ((BYTESPERLINE * 3) + (BYTESPERLINE + 2) + 1) -#define ASCIIOFFSET ((BYTESPERLINE * 3) + 2) -#define ISPRINT(c) ((c >= ' ') && (c <= '~')) - -/* - * Output a buffer formatted with a set number of bytes on - * each line. Append each line with the ASCII equivalent of - * each byte if it falls within the printable ASCII range, - * and '.' otherwise. - */ -static void -ds_dump_msg(void *vbuf, size_t len) -{ - int i, j; - char *curr; - char *aoff; - char line[LINEWIDTH]; - uint8_t *buf = vbuf; - - /* abort if not debugging ldc */ - if (!(ds_debug & DS_DBG_FLAG_MSG)) { - return; - } - - /* walk the buffer one line at a time */ - for (i = 0; i < len; i += BYTESPERLINE) { - - bzero(line, LINEWIDTH); - - curr = line; - aoff = line + ASCIIOFFSET; - - /* - * Walk the bytes in the current line, storing - * the hex value for the byte as well as the - * ASCII representation in a temporary buffer. - * All ASCII values are placed at the end of - * the line. - */ - for (j = 0; (j < BYTESPERLINE) && ((i + j) < len); j++) { - (void) sprintf(curr, " %02x", buf[i + j]); - *aoff = (ISPRINT(buf[i + j])) ? buf[i + j] : '.'; - curr += 3; - aoff++; - } - - /* - * Fill in to the start of the ASCII translation - * with spaces. This will only be necessary if - * this is the last line and there are not enough - * bytes to fill the whole line. - */ - while (curr != (line + ASCIIOFFSET)) - *curr++ = ' '; - - DS_DBG_MSG("%s\n", line); - } -} -#endif /* DEBUG */ - - -/* - * Walk the table of registered services, executing the specified - * callback function for each service. A non-zero return value from - * the callback is used to terminate the walk, not to indicate an - * error. Returns the index of the last service visited. - */ -static int -ds_walk_svcs(svc_cb_t svc_cb, void *arg) -{ - int idx; - ds_svc_t *svc; - - ASSERT(RW_WRITE_HELD(&ds_svcs.rwlock)); - - /* walk every table entry */ - for (idx = 0; idx < ds_svcs.maxsvcs; idx++) { - - svc = ds_svcs.tbl[idx]; - - /* execute the callback */ - if ((*svc_cb)(svc, arg) != 0) - break; - } - - return (idx); -} - -static int -ds_svc_isfree(ds_svc_t *svc, void *arg) -{ - _NOTE(ARGUNUSED(arg)) - - /* - * Looking for a free service. This may be a NULL entry - * in the table, or an unused structure that could be - * reused. - */ - - if (DS_SVC_ISFREE(svc)) { - /* yes, it is free */ - return (1); - } - - /* not a candidate */ - return (0); -} - -static int -ds_svc_ismatch(ds_svc_t *svc, void *arg) -{ - if (DS_SVC_ISFREE(svc)) { - return (0); - } - - if (strcmp(svc->cap.svc_id, arg) == 0) { - /* found a match */ - return (1); - } - - return (0); -} - -static int -ds_svc_free(ds_svc_t *svc, void *arg) -{ - _NOTE(ARGUNUSED(arg)) - - if (svc == NULL) { - return (0); - } - - if (svc->cap.svc_id) { - kmem_free(svc->cap.svc_id, strlen(svc->cap.svc_id) + 1); - svc->cap.svc_id = NULL; - } - - if (svc->cap.vers) { - kmem_free(svc->cap.vers, svc->cap.nvers * sizeof (ds_ver_t)); - svc->cap.vers = NULL; - } - - kmem_free(svc, sizeof (ds_svc_t)); - - return (0); -} - -static int -ds_svc_register(ds_svc_t *svc, void *arg) -{ - _NOTE(ARGUNUSED(arg)) - - int idx; - - ASSERT(RW_WRITE_HELD(&ds_svcs.rwlock)); - - /* check the state of the service */ - if (DS_SVC_ISFREE(svc) || (svc->state != DS_SVC_INACTIVE)) - return (0); - - /* check if there are any ports to try */ - if (DS_PORTSET_ISNULL(svc->avail)) - return (0); - - /* - * Attempt to register the service. Start with the lowest - * numbered port and continue until a registration message - * is sent successfully, or there are no ports left to try. - */ - for (idx = 0; idx < DS_MAX_PORTS; idx++) { - - /* - * If the port is not in the available list, - * it is not a candidate for registration. - */ - if (!DS_PORT_IN_SET(svc->avail, idx)) { - continue; - } - - svc->port = &ds_ports[idx]; - if (ds_send_reg_req(svc) == 0) { - /* register sent successfully */ - break; - } - - /* reset the service to try the next port */ - ds_reset_svc(svc, svc->port); - } - - return (0); -} - -static int -ds_svc_unregister(ds_svc_t *svc, void *arg) -{ - ds_port_t *port = (ds_port_t *)arg; - - ASSERT(RW_WRITE_HELD(&ds_svcs.rwlock)); - - if (DS_SVC_ISFREE(svc)) { - return (0); - } - - /* make sure the service is using this port */ - if (svc->port != port) { - return (0); - } - - DS_DBG("ds@%lx: svc_unreg: id='%s', ver=%d.%d, hdl=0x%09lx\n", port->id, - svc->cap.svc_id, svc->ver.major, svc->ver.minor, svc->hdl); - - /* reset the service structure */ - ds_reset_svc(svc, port); - - /* increment the count in the handle to prevent reuse */ - svc->hdl = DS_ALLOC_HDL(DS_HDL2IDX(svc->hdl), DS_HDL2COUNT(svc->hdl)); - - /* call the client unregister callback */ - if (svc->ops.ds_unreg_cb) - (*svc->ops.ds_unreg_cb)(svc->ops.cb_arg); - - /* try to initiate a new registration */ - (void) ds_svc_register(svc, NULL); - - return (0); -} - -static int -ds_svc_port_up(ds_svc_t *svc, void *arg) -{ - ds_port_t *port = (ds_port_t *)arg; - - if (DS_SVC_ISFREE(svc)) { - /* nothing to do */ - return (0); - } - - DS_PORTSET_ADD(svc->avail, port->id); - - return (0); -} - -static ds_svc_t * -ds_alloc_svc(void) -{ - int idx; - uint_t newmaxsvcs; - ds_svc_t **newtbl; - ds_svc_t *newsvc; - - ASSERT(RW_WRITE_HELD(&ds_svcs.rwlock)); - - idx = ds_walk_svcs(ds_svc_isfree, NULL); - - if (idx != ds_svcs.maxsvcs) { - goto found; - } - - /* - * There was no free space in the table. Grow - * the table to double its current size. - */ - newmaxsvcs = ds_svcs.maxsvcs * 2; - newtbl = kmem_zalloc(newmaxsvcs * sizeof (ds_svc_t *), KM_SLEEP); - - /* copy old table data to the new table */ - for (idx = 0; idx < ds_svcs.maxsvcs; idx++) { - newtbl[idx] = ds_svcs.tbl[idx]; - } - - /* clean up the old table */ - kmem_free(ds_svcs.tbl, ds_svcs.maxsvcs * sizeof (ds_svc_t *)); - ds_svcs.tbl = newtbl; - ds_svcs.maxsvcs = newmaxsvcs; - - /* search for a free space again */ - idx = ds_walk_svcs(ds_svc_isfree, NULL); - - /* the table is locked so should find a free slot */ - ASSERT(idx != ds_svcs.maxsvcs); - -found: - /* allocate a new svc structure if necessary */ - if ((newsvc = ds_svcs.tbl[idx]) == NULL) { - /* allocate a new service */ - newsvc = kmem_zalloc(sizeof (ds_svc_t), KM_SLEEP); - ds_svcs.tbl[idx] = newsvc; - } - - /* fill in the handle */ - newsvc->hdl = DS_ALLOC_HDL(idx, DS_HDL2COUNT(newsvc->hdl)); - - return (newsvc); -} - -static void -ds_reset_svc(ds_svc_t *svc, ds_port_t *port) -{ - ASSERT(RW_WRITE_HELD(&ds_svcs.rwlock)); - - svc->state = DS_SVC_INACTIVE; - svc->ver_idx = 0; - svc->ver.major = 0; - svc->ver.minor = 0; - svc->port = NULL; - DS_PORTSET_DEL(svc->avail, port->id); -} - -static ds_svc_t * -ds_get_svc(ds_svc_hdl_t hdl) -{ - int idx; - ds_svc_t *svc; - - ASSERT(RW_LOCK_HELD(&ds_svcs.rwlock)); - - if (hdl == DS_INVALID_HDL) - return (NULL); - - idx = DS_HDL2IDX(hdl); - - /* check if index is out of bounds */ - if ((idx < 0) || (idx >= ds_svcs.maxsvcs)) - return (NULL); - - svc = ds_svcs.tbl[idx]; - - /* check for a valid service */ - if (DS_SVC_ISFREE(svc)) - return (NULL); - - /* make sure the handle is an exact match */ - if (svc->hdl != hdl) - return (NULL); - - return (svc); -} - -static int -ds_port_add(md_t *mdp, mde_cookie_t port, mde_cookie_t chan) -{ - ds_port_t *newport; - uint64_t port_id; - uint64_t ldc_id; - - /* get the ID for this port */ - if (md_get_prop_val(mdp, port, "id", &port_id) != 0) { - cmn_err(CE_NOTE, "ds_port_add: port 'id' property not found"); - return (-1); - } - - /* sanity check the port id */ - if (port_id > DS_MAX_PORT_ID) { - cmn_err(CE_WARN, "ds_port_add: port ID %ld out of range", - port_id); - return (-1); - } - - DS_DBG("ds_port_add: adding port ds@%ld\n", port_id); - - /* get the channel ID for this port */ - if (md_get_prop_val(mdp, chan, "id", &ldc_id) != 0) { - cmn_err(CE_NOTE, "ds@%lx: add_port: no channel 'id' property", - port_id); - return (-1); - } - - /* get the port structure from the array of ports */ - newport = &ds_ports[port_id]; - - /* check for a duplicate port in the MD */ - if (newport->state != DS_PORT_FREE) { - cmn_err(CE_NOTE, "ds@%lx: add_port: port already exists", - port_id); - return (-1); - } - - /* initialize the port lock */ - mutex_init(&newport->lock, NULL, MUTEX_DRIVER, NULL); - - /* initialize the port */ - newport->id = port_id; - newport->state = DS_PORT_INIT; - newport->ldc.id = ldc_id; - - /* add the port to the set of all ports */ - DS_PORTSET_ADD(ds_allports, port_id); - - return (0); -} - -static void -ds_port_reset(ds_port_t *port) -{ - ASSERT(RW_WRITE_HELD(&ds_svcs.rwlock)); - ASSERT(MUTEX_HELD(&port->lock)); - - /* connection went down, mark everything inactive */ - (void) ds_walk_svcs(ds_svc_unregister, port); - - port->ver_idx = 0; - port->ver.major = 0; - port->ver.minor = 0; - port->state = DS_PORT_LDC_INIT; -} - -/* - * Verify that a version array is sorted as expected for the - * version negotiation to work correctly. - */ -static ds_vers_check_t -ds_vers_isvalid(ds_ver_t *vers, int nvers) -{ - uint16_t curr_major; - uint16_t curr_minor; - int idx; - - curr_major = vers[0].major; - curr_minor = vers[0].minor; - - /* - * Walk the version array, verifying correct ordering. - * The array must be sorted from highest supported - * version to lowest supported version. - */ - for (idx = 0; idx < nvers; idx++) { - if (vers[idx].major > curr_major) { - DS_DBG("vers_isvalid: version array has increasing " - "major versions\n"); - return (DS_VERS_INCREASING_MAJOR_ERR); - } - - if (vers[idx].major < curr_major) { - curr_major = vers[idx].major; - curr_minor = vers[idx].minor; - continue; - } - - if (vers[idx].minor > curr_minor) { - DS_DBG("vers_isvalid: version array has increasing " - "minor versions\n"); - return (DS_VERS_INCREASING_MINOR_ERR); - } - - curr_minor = vers[idx].minor; - } - - return (DS_VERS_OK); -} - -/* - * Logging Support - */ -static void -ds_log_init(void) -{ - ds_log_entry_t *new; - - /* initialize global lock */ - mutex_init(&ds_log.lock, NULL, MUTEX_DRIVER, NULL); - - mutex_enter(&ds_log.lock); - - /* initialize the log */ - ds_log.head = NULL; - ds_log.size = 0; - ds_log.nentry = 0; - - /* initialize the free list */ - for (new = ds_log_entry_pool; new < DS_LOG_POOL_END; new++) { - new->next = ds_log.freelist; - ds_log.freelist = new; - } - - mutex_exit(&ds_log.lock); - - DS_DBG_LOG("ds_log initialized: size=%d bytes, limit=%d bytes, " - "ninit=%ld\n", ds_log_sz, DS_LOG_LIMIT, DS_LOG_NPOOL); -} - -static void -ds_log_fini(void) -{ - ds_log_entry_t *next; - - mutex_enter(&ds_log.lock); - - /* clear out the log */ - while (ds_log.nentry > 0) - (void) ds_log_remove(); - - /* - * Now all the entries are on the free list. - * Clear out the free list, deallocating any - * entry that was dynamically allocated. - */ - while (ds_log.freelist != NULL) { - next = ds_log.freelist->next; - - if (!DS_IS_POOL_ENTRY(ds_log.freelist)) { - kmem_free(ds_log.freelist, sizeof (ds_log_entry_t)); - } - - ds_log.freelist = next; - } - - mutex_exit(&ds_log.lock); - - mutex_destroy(&ds_log.lock); -} - -static ds_log_entry_t * -ds_log_entry_alloc(void) -{ - ds_log_entry_t *new = NULL; - - ASSERT(MUTEX_HELD(&ds_log.lock)); - - if (ds_log.freelist != NULL) { - new = ds_log.freelist; - ds_log.freelist = ds_log.freelist->next; - } - - if (new == NULL) { - /* free list was empty */ - new = kmem_zalloc(sizeof (ds_log_entry_t), KM_SLEEP); - } - - ASSERT(new); - - return (new); -} - -static void -ds_log_entry_free(ds_log_entry_t *entry) -{ - ASSERT(MUTEX_HELD(&ds_log.lock)); - - if (entry == NULL) - return; - - if (entry->data != NULL) { - kmem_free(entry->data, entry->datasz); - entry->data = NULL; - } - - /* place entry on the free list */ - entry->next = ds_log.freelist; - ds_log.freelist = entry; -} - -/* - * Add a message to the end of the log - */ -static int -ds_log_add(ds_log_entry_t *new) -{ - ASSERT(MUTEX_HELD(&ds_log.lock)); - - if (ds_log.head == NULL) { - - new->prev = new; - new->next = new; - - ds_log.head = new; - } else { - ds_log_entry_t *head = ds_log.head; - ds_log_entry_t *tail = ds_log.head->prev; - - new->next = head; - new->prev = tail; - tail->next = new; - head->prev = new; - } - - /* increase the log size, including the metadata size */ - ds_log.size += DS_LOG_ENTRY_SZ(new); - ds_log.nentry++; - - DS_DBG_LOG("ds_log: added %ld data bytes, %ld total bytes\n", - new->datasz, DS_LOG_ENTRY_SZ(new)); - - return (0); -} - -/* - * Remove an entry from the head of the log - */ -static int -ds_log_remove(void) -{ - ds_log_entry_t *head; - - ASSERT(MUTEX_HELD(&ds_log.lock)); - - head = ds_log.head; - - /* empty list */ - if (head == NULL) - return (0); - - if (head->next == ds_log.head) { - /* one element list */ - ds_log.head = NULL; - } else { - head->next->prev = head->prev; - head->prev->next = head->next; - ds_log.head = head->next; - } - - DS_DBG_LOG("ds_log: removed %ld data bytes, %ld total bytes\n", - head->datasz, DS_LOG_ENTRY_SZ(head)); - - ds_log.size -= DS_LOG_ENTRY_SZ(head); - ds_log.nentry--; - - ds_log_entry_free(head); - - return (0); -} - -/* - * Replace the data in the entry at the front of the list with then - * new data. This has the effect of removing the oldest entry and - * adding the new entry. - */ -static int -ds_log_replace(int32_t dest, uint8_t *msg, size_t sz) -{ - ds_log_entry_t *head; - - ASSERT(MUTEX_HELD(&ds_log.lock)); - - head = ds_log.head; - - DS_DBG_LOG("ds_log: replaced %ld data bytes (%ld total) with %ld data " - "bytes (%ld total)\n", head->datasz, DS_LOG_ENTRY_SZ(head), - sz, sz + sizeof (ds_log_entry_t)); - - ds_log.size -= DS_LOG_ENTRY_SZ(head); - - kmem_free(head->data, head->datasz); - - head->data = msg; - head->datasz = sz; - head->timestamp = ddi_get_time(); - head->dest = dest; - - ds_log.size += DS_LOG_ENTRY_SZ(head); - - ds_log.head = head->next; - - return (0); -} - -static void -ds_log_purge(void *arg) -{ - _NOTE(ARGUNUSED(arg)) - - mutex_enter(&ds_log.lock); - - DS_DBG_LOG("ds_log: purging oldest log entries\n"); - - while ((ds_log.nentry) && (ds_log.size >= ds_log_sz)) { - (void) ds_log_remove(); - } - - mutex_exit(&ds_log.lock); -} - -static int -ds_log_add_msg(int32_t dest, uint8_t *msg, size_t sz) -{ - int rv = 0; - void *data; - - mutex_enter(&ds_log.lock); - - /* allocate a local copy of the data */ - data = kmem_alloc(sz, KM_SLEEP); - bcopy(msg, data, sz); - - /* check if the log is larger than the soft limit */ - if ((ds_log.nentry) && ((ds_log.size + sz) >= ds_log_sz)) { - /* - * The log is larger than the soft limit. - * Swap the oldest entry for the newest. - */ - DS_DBG_LOG("ds_log: replacing oldest entry with new entry\n"); - (void) ds_log_replace(dest, data, sz); - } else { - /* - * Still have headroom under the soft limit. - * Add the new entry to the log. - */ - ds_log_entry_t *new; - - new = ds_log_entry_alloc(); - - /* fill in message data */ - new->data = data; - new->datasz = sz; - new->timestamp = ddi_get_time(); - new->dest = dest; - - rv = ds_log_add(new); - } - - /* check if the log is larger than the hard limit */ - if ((ds_log.nentry > 1) && (ds_log.size >= DS_LOG_LIMIT)) { - /* - * Wakeup the thread to remove entries - * from the log until it is smaller than - * the soft limit. - */ - DS_DBG_LOG("ds_log: log exceeded %d bytes, scheduling a " - "purge...\n", DS_LOG_LIMIT); - - if (DS_DISPATCH(ds_log_purge, NULL) == NULL) { - cmn_err(CE_NOTE, "ds_log: purge thread failed to " - "start"); - } - } - - mutex_exit(&ds_log.lock); - - return (rv); -} - -/* - * Client Interface - */ - -int -ds_cap_init(ds_capability_t *cap, ds_clnt_ops_t *ops) -{ - int idx; - ds_vers_check_t status; - ds_svc_t *svc; - - /* sanity check the args */ - if ((cap == NULL) || (ops == NULL)) { - cmn_err(CE_NOTE, "ds_cap_init: invalid arguments"); - return (EINVAL); - } - - /* sanity check the capability specifier */ - if ((cap->svc_id == NULL) || (cap->vers == NULL) || (cap->nvers == 0)) { - cmn_err(CE_NOTE, "ds_cap_init: invalid capability specifier"); - return (EINVAL); - } - - /* sanity check the version array */ - if ((status = ds_vers_isvalid(cap->vers, cap->nvers)) != DS_VERS_OK) { - cmn_err(CE_NOTE, "ds_cap_init: invalid capability " - "version array for %s service: %s", cap->svc_id, - (status == DS_VERS_INCREASING_MAJOR_ERR) ? - "increasing major versions" : - "increasing minor versions"); - return (EINVAL); - } - - /* data and register callbacks are required */ - if ((ops->ds_data_cb == NULL) || (ops->ds_reg_cb == NULL)) { - cmn_err(CE_NOTE, "ds_cap_init: invalid ops specifier for " - "%s service", cap->svc_id); - return (EINVAL); - } - - DS_DBG("ds_cap_init: svc_id='%s', data_cb=0x%lx, cb_arg=0x%lx\n", - cap->svc_id, (uint64_t)ops->ds_data_cb, (uint64_t)ops->cb_arg); - - rw_enter(&ds_svcs.rwlock, RW_WRITER); - - /* check if the service is already registered */ - idx = ds_walk_svcs(ds_svc_ismatch, cap->svc_id); - if (idx != ds_svcs.maxsvcs) { - /* already registered */ - cmn_err(CE_NOTE, "service '%s' already registered", - cap->svc_id); - rw_exit(&ds_svcs.rwlock); - return (EALREADY); - } - - svc = ds_alloc_svc(); - - /* copy over all the client information */ - bcopy(cap, &svc->cap, sizeof (ds_capability_t)); - - /* make a copy of the service name */ - svc->cap.svc_id = kmem_zalloc(strlen(cap->svc_id) + 1, KM_SLEEP); - (void) strncpy(svc->cap.svc_id, cap->svc_id, strlen(cap->svc_id)); - - /* make a copy of the version array */ - svc->cap.vers = kmem_zalloc(cap->nvers * sizeof (ds_ver_t), KM_SLEEP); - bcopy(cap->vers, svc->cap.vers, cap->nvers * sizeof (ds_ver_t)); - - /* copy the client ops vector */ - bcopy(ops, &svc->ops, sizeof (ds_clnt_ops_t)); - - svc->state = DS_SVC_INACTIVE; - svc->ver_idx = 0; - DS_PORTSET_DUP(svc->avail, ds_allports); - - ds_svcs.nsvcs++; - - /* attempt to register the service */ - (void) ds_svc_register(svc, NULL); - - rw_exit(&ds_svcs.rwlock); - - DS_DBG("ds_cap_init: service '%s' assigned handle 0x%09lx\n", - svc->cap.svc_id, svc->hdl); - - return (0); -} - -int -ds_cap_fini(ds_capability_t *cap) -{ - int idx; - ds_svc_t *svc; - ds_svc_hdl_t tmp_hdl; - - rw_enter(&ds_svcs.rwlock, RW_WRITER); - - /* make sure the service is registered */ - idx = ds_walk_svcs(ds_svc_ismatch, cap->svc_id); - if (idx == ds_svcs.maxsvcs) { - /* service is not registered */ - cmn_err(CE_NOTE, "ds_cap_fini: unknown service '%s'", - cap->svc_id); - rw_exit(&ds_svcs.rwlock); - return (EINVAL); - } - - svc = ds_svcs.tbl[idx]; - - DS_DBG("ds_cap_fini: svcid='%s', hdl=0x%09lx\n", svc->cap.svc_id, - svc->hdl); - - /* - * Attempt to send an unregister notification. Even - * if sending the message fails, the local unregister - * request must be honored, since this indicates that - * the client will no longer handle incoming requests. - */ - (void) ds_send_unreg_req(svc); - - /* - * Clear out the structure, but do not deallocate the - * memory. It can be reused for the next registration. - */ - kmem_free(svc->cap.svc_id, strlen(svc->cap.svc_id) + 1); - kmem_free(svc->cap.vers, svc->cap.nvers * sizeof (ds_ver_t)); - - /* save the handle to prevent reuse */ - tmp_hdl = svc->hdl; - bzero(svc, sizeof (ds_svc_t)); - - /* initialize for next use */ - svc->hdl = tmp_hdl; - svc->state = DS_SVC_FREE; - - ds_svcs.nsvcs--; - - rw_exit(&ds_svcs.rwlock); - - return (0); -} - -int -ds_cap_send(ds_svc_hdl_t hdl, void *buf, size_t len) -{ - int rv; - ds_hdr_t *hdr; - caddr_t msg; - size_t msglen; - size_t hdrlen; - caddr_t payload; - ds_svc_t *svc; - ds_port_t *port; - ds_data_handle_t *data; - - rw_enter(&ds_svcs.rwlock, RW_READER); - - if ((hdl == DS_INVALID_HDL) || (svc = ds_get_svc(hdl)) == NULL) { - cmn_err(CE_NOTE, "ds_cap_send: invalid handle 0x%09lx", hdl); - rw_exit(&ds_svcs.rwlock); - return (EINVAL); - } - - if ((port = svc->port) == NULL) { - cmn_err(CE_NOTE, "ds_cap_send: service '%s' not associated " - "with a port", svc->cap.svc_id); - rw_exit(&ds_svcs.rwlock); - return (ECONNRESET); - } - - mutex_enter(&port->lock); - - /* check that the LDC channel is ready */ - if (port->ldc.state != LDC_UP) { - cmn_err(CE_NOTE, "ds_cap_send: LDC channel is not up"); - mutex_exit(&port->lock); - rw_exit(&ds_svcs.rwlock); - return (ECONNRESET); - } - - - if (svc->state != DS_SVC_ACTIVE) { - /* channel is up, but svc is not registered */ - cmn_err(CE_NOTE, "ds_cap_send: invalid service state 0x%x", - svc->state); - mutex_exit(&port->lock); - rw_exit(&ds_svcs.rwlock); - return (EINVAL); - } - - mutex_exit(&port->lock); - rw_exit(&ds_svcs.rwlock); - - hdrlen = DS_HDR_SZ + sizeof (ds_data_handle_t); - - msg = kmem_zalloc(len + hdrlen, KM_SLEEP); - hdr = (ds_hdr_t *)msg; - payload = msg + hdrlen; - msglen = len + hdrlen; - - hdr->payload_len = len + sizeof (ds_data_handle_t); - hdr->msg_type = DS_DATA; - - data = (ds_data_handle_t *)(msg + DS_HDR_SZ); - data->svc_handle = hdl; - - if ((buf != NULL) && (len != 0)) { - bcopy(buf, payload, len); - } - - DS_DBG("ds@%lx: data>: hdl=0x%09lx, len=%ld, payload_len=%d\n", - port->id, svc->hdl, msglen, hdr->payload_len); - - mutex_enter(&port->lock); - - if ((rv = ds_send_msg(port, msg, msglen)) != 0) { - rv = (rv == EIO) ? ECONNRESET : rv; - } - - mutex_exit(&port->lock); - - kmem_free(msg, msglen); - - - return (rv); -} - -/* - * Specific errno's that are used by ds.c and ldc.c - */ -static struct { - int errno; - char *estr; -} ds_errno_to_str_tab[] = { - EIO, "I/O error", - EAGAIN, "Resource temporarily unavailable", - ENOMEM, "Not enough space", - EACCES, "Permission denied", - EFAULT, "Bad address", - EBUSY, "Device busy", - EINVAL, "Invalid argument", - ENOSPC, "No space left on device", - ECHRNG, "Channel number out of range", - ENOTSUP, "Operation not supported", - EMSGSIZE, "Message too long", - EADDRINUSE, "Address already in use", - ECONNRESET, "Connection reset by peer", - ENOBUFS, "No buffer space available", - ECONNREFUSED, "Connection refused", - EALREADY, "Operation already in progress", - 0, -}; - -static char * -ds_errno_to_str(int errno, char *ebuf) -{ - int i, en; - - for (i = 0; (en = ds_errno_to_str_tab[i].errno) != 0; i++) { - if (en == errno) { - (void) strcpy(ebuf, ds_errno_to_str_tab[i].estr); - return (ebuf); - } - } - - (void) sprintf(ebuf, "errno (%d)", errno); - return (ebuf); -} diff --git a/usr/src/uts/sun4v/io/ds_common.c b/usr/src/uts/sun4v/io/ds_common.c new file mode 100644 index 0000000000..0d3a354780 --- /dev/null +++ b/usr/src/uts/sun4v/io/ds_common.c @@ -0,0 +1,3141 @@ +/* + * 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. + */ + +/* + * Domain Services Module Common Code. + * + * This module is intended to be used by both Solaris and the VBSC + * module. + */ + +#include <sys/modctl.h> +#include <sys/ksynch.h> +#include <sys/taskq.h> +#include <sys/disp.h> +#include <sys/cmn_err.h> +#include <sys/note.h> +#include <sys/mach_descrip.h> +#include <sys/mdesc.h> +#include <sys/ldc.h> +#include <sys/ds.h> +#include <sys/ds_impl.h> + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define DS_DECODE_BUF_LEN 30 + +/* + * All DS ports in the system + * + * The list of DS ports is read in from the MD when the DS module is + * initialized and is never modified. This eliminates the need for + * locking to access the port array itself. Access to the individual + * ports are synchronized at the port level. + */ +ds_port_t ds_ports[DS_MAX_PORTS]; +ds_portset_t ds_allports; /* all DS ports in the system */ + +/* + * Table of registered services + * + * Locking: Accesses to the table of services are synchronized using + * a mutex lock. The reader lock must be held when looking up service + * information in the table. The writer lock must be held when any + * service information is being modified. + */ +ds_svcs_t ds_svcs; + +/* + * Flag to prevent callbacks while in the middle of DS teardown. + */ +boolean_t ds_enabled = B_FALSE; /* enable/disable taskq processing */ + +/* + * Retry count and delay for LDC reads and writes + */ +#ifndef DS_DEFAULT_RETRIES +#define DS_DEFAULT_RETRIES 10000 /* number of times to retry */ +#endif +#ifndef DS_DEFAULT_DELAY +#define DS_DEFAULT_DELAY 1000 /* usecs to wait between retries */ +#endif + +static int ds_retries = DS_DEFAULT_RETRIES; +static clock_t ds_delay = DS_DEFAULT_DELAY; + +/* + * Supported versions of the DS message protocol + * + * The version array must be sorted in order from the highest + * supported version to the lowest. Support for a particular + * <major>.<minor> version implies all lower minor versions of + * that same major version are supported as well. + */ +static ds_ver_t ds_vers[] = { { 1, 0 } }; + +#define DS_NUM_VER (sizeof (ds_vers) / sizeof (ds_vers[0])) + + +/* incoming message handling functions */ +typedef void (*ds_msg_handler_t)(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_init_req(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_init_ack(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_init_nack(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_reg_req(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_reg_ack(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_reg_nack(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_unreg_req(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_unreg_ack(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_unreg_nack(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_data(ds_port_t *port, caddr_t buf, size_t len); +static void ds_handle_nack(ds_port_t *port, caddr_t buf, size_t len); + +/* + * DS Message Handler Dispatch Table + * + * A table used to dispatch all incoming messages. This table + * contains handlers for all the fixed message types, as well as + * the the messages defined in the 1.0 version of the DS protocol. + * The handlers are indexed based on the DS header msg_type values + */ +static const ds_msg_handler_t ds_msg_handlers[] = { + ds_handle_init_req, /* DS_INIT_REQ */ + ds_handle_init_ack, /* DS_INIT_ACK */ + ds_handle_init_nack, /* DS_INIT_NACK */ + ds_handle_reg_req, /* DS_REG_REQ */ + ds_handle_reg_ack, /* DS_REG_ACK */ + ds_handle_reg_nack, /* DS_REG_NACK */ + ds_handle_unreg_req, /* DS_UNREG */ + ds_handle_unreg_ack, /* DS_UNREG_ACK */ + ds_handle_unreg_nack, /* DS_UNREG_NACK */ + ds_handle_data, /* DS_DATA */ + ds_handle_nack /* DS_NACK */ +}; + + + +/* initialization functions */ +static int ds_ldc_init(ds_port_t *port); + +/* event processing functions */ +static uint_t ds_ldc_cb(uint64_t event, caddr_t arg); +static int ds_recv_msg(ds_port_t *port, caddr_t msgp, size_t *sizep); +static void ds_handle_up_event(ds_port_t *port); +static void ds_handle_down_reset_events(ds_port_t *port); +static void ds_handle_recv(void *arg); +static void ds_dispatch_event(void *arg); + +/* message sending functions */ +static int ds_send_msg(ds_port_t *port, caddr_t msg, size_t msglen); +static int ds_send_reg_req(ds_svc_t *svc, ds_port_t *port); +static void ds_send_unreg_nack(ds_port_t *port, ds_svc_hdl_t bad_hdl); +static void ds_send_data_nack(ds_port_t *port, ds_svc_hdl_t bad_hdl); + +/* walker functions */ +static int ds_svc_isfree(ds_svc_t *svc, void *arg); +static int ds_svc_unregister(ds_svc_t *svc, void *arg); +static int ds_svc_port_up(ds_svc_t *svc, void *arg); + +/* service utilities */ +static void ds_reset_svc(ds_svc_t *svc, ds_port_t *port); +static int ds_svc_register_onport(ds_svc_t *svc, ds_port_t *port); + +/* port utilities */ +static void ds_port_reset(ds_port_t *port); +static ldc_status_t ds_update_ldc_state(ds_port_t *port); + +/* misc utilities */ +static void min_max_versions(int num_versions, ds_ver_t *sup_versionsp, + uint16_t *min_major, uint16_t *max_major); + +/* debug */ +static char *decode_ldc_events(uint64_t event, char *buf); + +/* loopback */ +static void ds_loopback_register(ds_svc_hdl_t hdl); +static void ds_loopback_unregister(ds_svc_hdl_t hdl); +static void ds_loopback_send(ds_svc_hdl_t hdl, void *buf, size_t buflen); +static int ds_loopback_set_svc(ds_svc_t *svc, ds_svc_hdl_t lb_hdl); + +/* client handling */ +static int i_ds_hdl_lookup(char *service, uint_t is_client, ds_svc_hdl_t *hdlp, + uint_t maxhdls); +static ds_svc_t *ds_find_clnt_svc_by_hdl_port(ds_svc_hdl_t hdl, + ds_port_t *port); +static ds_svc_t *ds_find_svc_by_id_port(char *svc_id, int is_client, + ds_port_t *port); +static ds_svc_t *ds_svc_clone(ds_svc_t *svc); +static void ds_portset_del_active_clients(char *service, ds_portset_t *portsp); +static void ds_check_for_dup_services(ds_svc_t *svc); +static void ds_delete_svc_entry(ds_svc_t *svc); + +char * +ds_strdup(char *str) +{ + char *newstr; + + newstr = DS_MALLOC(strlen(str) + 1); + (void) strcpy(newstr, str); + return (newstr); +} + +void +ds_common_init(void) +{ + /* Validate version table */ + ASSERT(ds_vers_isvalid(ds_vers, DS_NUM_VER) == DS_VERS_OK); + + /* Initialize services table */ + ds_init_svcs_tbl(DS_MAXSVCS_INIT); + + /* enable callback processing */ + ds_enabled = B_TRUE; +} + +/* BEGIN LDC SUPPORT FUNCTIONS */ + +static char * +decode_ldc_events(uint64_t event, char *buf) +{ + buf[0] = 0; + if (event & LDC_EVT_DOWN) (void) strcat(buf, " DOWN"); + if (event & LDC_EVT_RESET) (void) strcat(buf, " RESET"); + if (event & LDC_EVT_UP) (void) strcat(buf, " UP"); + if (event & LDC_EVT_READ) (void) strcat(buf, " READ"); + if (event & LDC_EVT_WRITE) (void) strcat(buf, " WRITE"); + return (buf); +} + +static ldc_status_t +ds_update_ldc_state(ds_port_t *port) +{ + ldc_status_t ldc_state; + int rv; + char ebuf[DS_EBUFSIZE]; + + ASSERT(MUTEX_HELD(&port->lock)); + + /* + * Read status and update ldc state info in port structure. + */ + if ((rv = ldc_status(port->ldc.hdl, &ldc_state)) != 0) { + cmn_err(CE_WARN, "ds@%lx: %s: ldc_status error: %s" DS_EOL, + PORTID(port), __func__, ds_errno_to_str(rv, ebuf)); + ldc_state = port->ldc.state; + } else { + port->ldc.state = ldc_state; + } + + return (ldc_state); +} + +static void +ds_handle_down_reset_events(ds_port_t *port) +{ + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: entered" DS_EOL, PORTID(port), + __func__); + + mutex_enter(&ds_svcs.lock); + mutex_enter(&port->lock); + + ds_sys_drain_events(port); + + (void) ds_update_ldc_state(port); + + /* reset the port state */ + ds_port_reset(port); + + /* acknowledge the reset */ + (void) ldc_up(port->ldc.hdl); + + mutex_exit(&port->lock); + mutex_exit(&ds_svcs.lock); + + ds_handle_up_event(port); + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: exit" DS_EOL, PORTID(port), __func__); +} + +static void +ds_handle_up_event(ds_port_t *port) +{ + ldc_status_t ldc_state; + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: entered" DS_EOL, PORTID(port), + __func__); + + mutex_enter(&port->lock); + + ldc_state = ds_update_ldc_state(port); + + mutex_exit(&port->lock); + + if ((ldc_state == LDC_UP) && IS_DS_PORT(port)) { + /* + * Initiate the handshake. + */ + ds_send_init_req(port); + } + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: exit" DS_EOL, PORTID(port), __func__); +} + +static uint_t +ds_ldc_cb(uint64_t event, caddr_t arg) +{ + ds_port_t *port = (ds_port_t *)arg; + char evstring[DS_DECODE_BUF_LEN]; + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: %s event (%llx) received" DS_EOL, + PORTID(port), __func__, decode_ldc_events(event, evstring), + (u_longlong_t)event); + + if (!ds_enabled) { + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: callback handling is disabled" + DS_EOL, PORTID(port), __func__); + return (LDC_SUCCESS); + } + + if (event & (LDC_EVT_DOWN | LDC_EVT_RESET)) { + ds_handle_down_reset_events(port); + goto done; + } + + if (event & LDC_EVT_UP) { + ds_handle_up_event(port); + } + + if (event & LDC_EVT_READ) { + if (port->ldc.state != LDC_UP) { + cmn_err(CE_WARN, "ds@%lx: %s: LDC READ event while " + "port not up" DS_EOL, PORTID(port), __func__); + goto done; + } + + if (ds_sys_dispatch_func(ds_handle_recv, port)) { + cmn_err(CE_WARN, "ds@%lx: error initiating LDC READ " + " event", PORTID(port)); + } + } + + if (event & LDC_EVT_WRITE) { + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: LDC WRITE event received, " + "not supported" DS_EOL, PORTID(port), __func__); + } + + if (event & ~(LDC_EVT_UP | LDC_EVT_READ)) { + cmn_err(CE_WARN, "ds@%lx: %s: Unexpected LDC event received: " + "0x%llx" DS_EOL, PORTID(port), __func__, + (u_longlong_t)event); + } +done: + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: exit" DS_EOL, PORTID(port), __func__); + + return (LDC_SUCCESS); +} + +static int +ds_ldc_init(ds_port_t *port) +{ + int rv; + ldc_attr_t ldc_attr; + caddr_t ldc_cb_arg = (caddr_t)port; + char ebuf[DS_EBUFSIZE]; + + ASSERT(MUTEX_HELD(&port->lock)); + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: ldc_id=%lld" DS_EOL, + PORTID(port), __func__, (u_longlong_t)port->ldc.id); + + ldc_attr.devclass = LDC_DEV_GENERIC; + ldc_attr.instance = 0; + ldc_attr.mode = LDC_MODE_RELIABLE; + ldc_attr.mtu = DS_STREAM_MTU; + + if ((rv = ldc_init(port->ldc.id, &ldc_attr, &port->ldc.hdl)) != 0) { + cmn_err(CE_WARN, "ds@%lx: %s: ldc_id: %lx, ldc_init error: %s" + DS_EOL, PORTID(port), __func__, port->ldc.id, + ds_errno_to_str(rv, ebuf)); + return (rv); + } + + rv = ldc_reg_callback(port->ldc.hdl, ds_ldc_cb, ldc_cb_arg); + if (rv != 0) { + cmn_err(CE_WARN, "ds@%lx: %s: ldc_reg_callback error: %s" + DS_EOL, PORTID(port), __func__, ds_errno_to_str(rv, ebuf)); + return (rv); + } + + ds_sys_ldc_init(port); + return (0); +} + +int +ds_ldc_fini(ds_port_t *port) +{ + int rv; + char ebuf[DS_EBUFSIZE]; + + ASSERT(port->state >= DS_PORT_LDC_INIT); + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: ldc_id=%ld" DS_EOL, PORTID(port), + __func__, port->ldc.id); + + if ((rv = ldc_close(port->ldc.hdl)) != 0) { + cmn_err(CE_WARN, "ds@%lx: %s: ldc_close error: %s" DS_EOL, + PORTID(port), __func__, ds_errno_to_str(rv, ebuf)); + return (rv); + } + + if ((rv = ldc_unreg_callback(port->ldc.hdl)) != 0) { + cmn_err(CE_WARN, "ds@%lx: %s: ldc_unreg_callback error: %s" + DS_EOL, PORTID(port), __func__, ds_errno_to_str(rv, ebuf)); + return (rv); + } + + if ((rv = ldc_fini(port->ldc.hdl)) != 0) { + cmn_err(CE_WARN, "ds@%lx: %s: ldc_fini error: %s" DS_EOL, + PORTID(port), __func__, ds_errno_to_str(rv, ebuf)); + return (rv); + } + + return (rv); +} + +/* + * Attempt to read a specified number of bytes from a particular LDC. + * Returns zero for success or the return code from the LDC read on + * failure. The actual number of bytes read from the LDC is returned + * in the size parameter. + */ +static int +ds_recv_msg(ds_port_t *port, caddr_t msgp, size_t *sizep) +{ + int rv = 0; + size_t bytes_req = *sizep; + size_t bytes_left = bytes_req; + size_t nbytes; + int retry_count = 0; + char ebuf[DS_EBUFSIZE]; + + ASSERT(MUTEX_HELD(&port->rcv_lock)); + + *sizep = 0; + + DS_DBG_LDC(CE_NOTE, "ds@%lx: attempting to read %ld bytes" DS_EOL, + PORTID(port), bytes_req); + + while (bytes_left > 0) { + + nbytes = bytes_left; + + if ((rv = ldc_read(port->ldc.hdl, msgp, &nbytes)) != 0) { + if (rv == ECONNRESET) { + break; + } else if (rv != EAGAIN) { + cmn_err(CE_NOTE, "ds@%lx: %s: %s" DS_EOL, + PORTID(port), __func__, + ds_errno_to_str(rv, ebuf)); + break; + } + } else { + if (nbytes != 0) { + DS_DBG_LDC(CE_NOTE, "ds@%lx: " + "read %ld bytes, %d retries" DS_EOL, + PORTID(port), nbytes, retry_count); + + *sizep += nbytes; + msgp += nbytes; + bytes_left -= nbytes; + + /* reset counter on a successful read */ + retry_count = 0; + continue; + } + + /* + * No data was read. Check if this is the + * first attempt. If so, just return since + * nothing has been read yet. + */ + if (bytes_left == bytes_req) { + DS_DBG_LDC(CE_NOTE, "ds@%lx: read zero bytes, " + " no data available" DS_EOL, PORTID(port)); + break; + } + } + + /* + * A retry is necessary because the read returned + * EAGAIN, or a zero length read occurred after + * reading a partial message. + */ + if (retry_count++ >= ds_retries) { + DS_DBG_LDC(CE_NOTE, "ds@%lx: timed out waiting for " + "message" DS_EOL, PORTID(port)); + break; + } + + drv_usecwait(ds_delay); + } + + return (rv); +} + +static void +ds_handle_recv(void *arg) +{ + ds_port_t *port = (ds_port_t *)arg; + char *hbuf; + size_t msglen; + size_t read_size; + boolean_t hasdata; + ds_hdr_t hdr; + uint8_t *msg; + char *currp; + int rv; + ds_event_t *devent; + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s..." DS_EOL, PORTID(port), __func__); + + /* + * Read messages from the channel until there are none + * pending. Valid messages are dispatched to be handled + * by a separate thread while any malformed messages are + * dropped. + */ + + mutex_enter(&port->rcv_lock); + + while (((rv = ldc_chkq(port->ldc.hdl, &hasdata)) == 0) && hasdata) { + + DS_DBG(CE_NOTE, "ds@%lx: %s: reading next message" DS_EOL, + PORTID(port), __func__); + + /* + * Read in the next message. + */ + hbuf = (char *)&hdr; + bzero(hbuf, DS_HDR_SZ); + read_size = DS_HDR_SZ; + currp = hbuf; + + /* read in the message header */ + if ((rv = ds_recv_msg(port, currp, &read_size)) != 0) { + break; + } + + if (read_size < DS_HDR_SZ) { + /* + * A zero length read is a valid signal that + * there is no data left on the channel. + */ + if (read_size != 0) { + cmn_err(CE_WARN, "ds@%lx: invalid message " + "length, received %ld bytes, expected %ld" + DS_EOL, PORTID(port), read_size, DS_HDR_SZ); + } + continue; + } + + /* get payload size and allocate a buffer */ + read_size = ((ds_hdr_t *)hbuf)->payload_len; + msglen = DS_HDR_SZ + read_size; + msg = DS_MALLOC(msglen); + if (!msg) { + cmn_err(CE_WARN, "Memory allocation failed attempting " + " to allocate %d bytes." DS_EOL, (int)msglen); + continue; + } + + DS_DBG(CE_NOTE, "ds@%lx: %s: message payload len %d" DS_EOL, + PORTID(port), __func__, (int)read_size); + + /* move message header into buffer */ + (void) memcpy(msg, hbuf, DS_HDR_SZ); + currp = (char *)(msg) + DS_HDR_SZ; + + /* read in the message body */ + if ((rv = ds_recv_msg(port, currp, &read_size)) != 0) { + DS_FREE(msg, msglen); + break; + } + + /* validate the size of the message */ + if ((DS_HDR_SZ + read_size) != msglen) { + cmn_err(CE_WARN, "ds@%lx: %s: invalid message length, " + "received %ld bytes, expected %ld" DS_EOL, + PORTID(port), __func__, (DS_HDR_SZ + read_size), + msglen); + DS_FREE(msg, msglen); + continue; + } + + DS_DUMP_MSG(DS_DBG_FLAG_LDC, msg, msglen); + + /* + * Send the message for processing, and store it + * in the log. The memory is deallocated only when + * the message is removed from the log. + */ + + devent = DS_MALLOC(sizeof (ds_event_t)); + devent->port = port; + devent->buf = (char *)msg; + devent->buflen = msglen; + + /* log the message */ + (void) ds_log_add_msg(DS_LOG_IN(port->id), msg, msglen); + + if (ds_sys_dispatch_func(ds_dispatch_event, devent)) { + cmn_err(CE_WARN, "ds@%lx: error initiating " + "event handler", PORTID(port)); + DS_FREE(devent, sizeof (ds_event_t)); + } + } + + mutex_exit(&port->rcv_lock); + + /* handle connection reset errors returned from ds_recv_msg */ + if (rv == ECONNRESET) { + ds_handle_down_reset_events(port); + } + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s done" DS_EOL, PORTID(port), __func__); +} + +static void +ds_dispatch_event(void *arg) +{ + ds_event_t *event = (ds_event_t *)arg; + ds_hdr_t *hdr; + ds_port_t *port; + + port = event->port; + + hdr = (ds_hdr_t *)event->buf; + + if (DS_MSG_TYPE_VALID(hdr->msg_type)) { + DS_DBG(CE_NOTE, "ds@%lx: dispatch_event: msg_type=%d" DS_EOL, + PORTID(port), hdr->msg_type); + + (*ds_msg_handlers[hdr->msg_type])(port, event->buf, + event->buflen); + } else { + cmn_err(CE_WARN, "ds@%lx: dispatch_event: invalid msg " + "type (%d)" DS_EOL, PORTID(port), hdr->msg_type); + } + + DS_FREE(event->buf, event->buflen); + DS_FREE(event, sizeof (ds_event_t)); +} + +int +ds_send_msg(ds_port_t *port, caddr_t msg, size_t msglen) +{ + int rv; + caddr_t currp = msg; + size_t amt_left = msglen; + int loopcnt = 0; + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s msglen: %ld" DS_EOL, PORTID(port), + __func__, msglen); + DS_DUMP_MSG(DS_DBG_FLAG_LDC, msg, msglen); + + /* + * Ensure that no other messages can be sent on this port by holding + * the tx_lock mutex in case the write doesn't get sent with one write. + * This guarantees that the message doesn't become fragmented. + */ + mutex_enter(&port->tx_lock); + + do { + if ((rv = ldc_write(port->ldc.hdl, currp, &msglen)) != 0) { + if (rv == ECONNRESET) { + mutex_exit(&port->tx_lock); + ds_handle_down_reset_events(port); + return (rv); + } else if ((rv == EWOULDBLOCK) && + (loopcnt++ < ds_retries)) { + drv_usecwait(ds_delay); + } else { + cmn_err(CE_WARN, "ds@%lx: send_msg: ldc_write " + "failed (%d), %d bytes remaining" DS_EOL, + PORTID(port), rv, (int)amt_left); + goto error; + } + } else { + amt_left -= msglen; + currp += msglen; + msglen = amt_left; + loopcnt = 0; + } + } while (amt_left > 0); +error: + mutex_exit(&port->tx_lock); + + return (rv); +} + +/* END LDC SUPPORT FUNCTIONS */ + + +/* BEGIN DS PROTOCOL SUPPORT FUNCTIONS */ + +static void +ds_handle_init_req(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_hdr_t *hdr; + ds_init_ack_t *ack; + ds_init_nack_t *nack; + char *msg; + size_t msglen; + ds_init_req_t *req; + size_t explen = DS_MSG_LEN(ds_init_req_t); + uint16_t new_major; + uint16_t new_minor; + boolean_t match; + + /* sanity check the incoming message */ + if (len != explen) { + cmn_err(CE_WARN, "ds@%lx: <init_req: invalid message " + "length (%ld), expected %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + req = (ds_init_req_t *)(buf + DS_HDR_SZ); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <init_req: ver=%d.%d" DS_EOL, + PORTID(port), req->major_vers, req->minor_vers); + + match = negotiate_version(DS_NUM_VER, &ds_vers[0], + req->major_vers, &new_major, &new_minor); + + /* + * Check version info. ACK only if the major numbers exactly + * match. The service entity can retry with a new minor + * based on the response sent as part of the NACK. + */ + if (match) { + msglen = DS_MSG_LEN(ds_init_ack_t); + msg = DS_MALLOC(msglen); + + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_INIT_ACK; + hdr->payload_len = sizeof (ds_init_ack_t); + + ack = (ds_init_ack_t *)(msg + DS_HDR_SZ); + ack->minor_vers = MIN(new_minor, req->minor_vers); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: init_ack>: minor=0x%04X" DS_EOL, + PORTID(port), MIN(new_minor, req->minor_vers)); + } else { + msglen = DS_MSG_LEN(ds_init_nack_t); + msg = DS_MALLOC(msglen); + + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_INIT_NACK; + hdr->payload_len = sizeof (ds_init_nack_t); + + nack = (ds_init_nack_t *)(msg + DS_HDR_SZ); + nack->major_vers = new_major; + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: init_nack>: major=0x%04X" DS_EOL, + PORTID(port), new_major); + } + + /* + * Send the response + */ + (void) ds_send_msg(port, msg, msglen); + DS_FREE(msg, msglen); +} + +static void +ds_handle_init_ack(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_init_ack_t *ack; + ds_ver_t *ver; + size_t explen = DS_MSG_LEN(ds_init_ack_t); + + /* sanity check the incoming message */ + if (len != explen) { + cmn_err(CE_WARN, "ds@%lx: <init_ack: invalid message " + "length (%ld), expected %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + ack = (ds_init_ack_t *)(buf + DS_HDR_SZ); + + mutex_enter(&port->lock); + + if (port->state != DS_PORT_INIT_REQ) { + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <init_ack: invalid state: %d" + DS_EOL, PORTID(port), port->state); + mutex_exit(&port->lock); + return; + } + + ver = &(ds_vers[port->ver_idx]); + + /* agreed upon a major version */ + port->ver.major = ver->major; + + /* + * If the returned minor version is larger than + * the requested minor version, use the lower of + * the two, i.e. the requested version. + */ + if (ack->minor_vers >= ver->minor) { + /* + * Use the minor version specified in the + * original request. + */ + port->ver.minor = ver->minor; + } else { + /* + * Use the lower minor version returned in + * the ack. By definition, all lower minor + * versions must be supported. + */ + port->ver.minor = ack->minor_vers; + } + + port->state = DS_PORT_READY; + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <init_ack: port ready v%d.%d" DS_EOL, + PORTID(port), port->ver.major, port->ver.minor); + + mutex_exit(&port->lock); + + /* + * The port came up, so update all the services + * with this information. Follow that up with an + * attempt to register any service that is not + * already registered. + */ + mutex_enter(&ds_svcs.lock); + + (void) ds_walk_svcs(ds_svc_port_up, port); + (void) ds_walk_svcs(ds_svc_register, NULL); + + mutex_exit(&ds_svcs.lock); +} + +static void +ds_handle_init_nack(ds_port_t *port, caddr_t buf, size_t len) +{ + int idx; + ds_init_nack_t *nack; + ds_ver_t *ver; + size_t explen = DS_MSG_LEN(ds_init_nack_t); + + /* sanity check the incoming message */ + if (len != explen) { + DS_DBG_PRCL(CE_WARN, "ds@%lx: <init_nack: invalid message " + "length (%ld), expected %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + nack = (ds_init_nack_t *)(buf + DS_HDR_SZ); + + mutex_enter(&port->lock); + + if (port->state != DS_PORT_INIT_REQ) { + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <init_nack: invalid state: %d" + DS_EOL, PORTID(port), port->state); + mutex_exit(&port->lock); + return; + } + + ver = &(ds_vers[port->ver_idx]); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <init_nack: req=v%d.%d, nack=v%d.x" + DS_EOL, PORTID(port), ver->major, ver->minor, nack->major_vers); + + if (nack->major_vers == 0) { + /* no supported protocol version */ + DS_DBG_PRCL(CE_WARN, "ds@%lx: <init_nack: DS not supported" + DS_EOL, PORTID(port)); + mutex_exit(&port->lock); + return; + } + + /* + * Walk the version list, looking for a major version + * that is as close to the requested major version as + * possible. + */ + for (idx = port->ver_idx; idx < DS_NUM_VER; idx++) { + if (ds_vers[idx].major <= nack->major_vers) { + /* found a version to try */ + goto done; + } + } + + if (idx == DS_NUM_VER) { + /* no supported version */ + DS_DBG_PRCL(CE_WARN, "ds@%lx: <init_nack: DS v%d.x not " + "supported" DS_EOL, PORTID(port), nack->major_vers); + + mutex_exit(&port->lock); + return; + } + +done: + /* start the handshake again */ + port->ver_idx = idx; + port->state = DS_PORT_LDC_INIT; + mutex_exit(&port->lock); + + ds_send_init_req(port); + +} + +static ds_svc_t * +ds_find_svc_by_id_port(char *svc_id, int is_client, ds_port_t *port) +{ + int idx; + ds_svc_t *svc, *found_svc = 0; + uint32_t flag_match = is_client ? DSSF_ISCLIENT : 0; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + /* walk every table entry */ + for (idx = 0; idx < ds_svcs.maxsvcs; idx++) { + svc = ds_svcs.tbl[idx]; + if (DS_SVC_ISFREE(svc)) + continue; + if (strcmp(svc->cap.svc_id, svc_id) != 0) + continue; + if ((svc->flags & DSSF_ISCLIENT) != flag_match) + continue; + if (port != NULL && svc->port == port) { + return (svc); + } else if (svc->state == DS_SVC_INACTIVE) { + found_svc = svc; + } else if (!found_svc) { + found_svc = svc; + } + } + + return (found_svc); +} + +static void +ds_handle_reg_req(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_reg_req_t *req; + ds_hdr_t *hdr; + ds_reg_ack_t *ack; + ds_reg_nack_t *nack; + char *msg; + size_t msglen; + size_t explen = DS_MSG_LEN(ds_reg_req_t); + ds_svc_t *svc = NULL; + ds_ver_t version; + uint16_t new_major; + uint16_t new_minor; + boolean_t match; + + /* sanity check the incoming message */ + if (len < explen) { + cmn_err(CE_WARN, "ds@%lx: <reg_req: invalid message " + "length (%ld), expected at least %ld" DS_EOL, + PORTID(port), len, explen); + return; + } + + req = (ds_reg_req_t *)(buf + DS_HDR_SZ); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <reg_req: '%s' ver=%d.%d, hdl=0x%llx" + DS_EOL, PORTID(port), req->svc_id, req->major_vers, req->minor_vers, + (u_longlong_t)req->svc_handle); + + mutex_enter(&ds_svcs.lock); + svc = ds_find_svc_by_id_port(req->svc_id, + DS_HDL_ISCLIENT(req->svc_handle) == 0, port); + if (svc == NULL) { + +do_reg_nack: + mutex_exit(&ds_svcs.lock); + + msglen = DS_MSG_LEN(ds_reg_nack_t); + msg = DS_MALLOC(msglen); + + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_REG_NACK; + hdr->payload_len = sizeof (ds_reg_nack_t); + + nack = (ds_reg_nack_t *)(msg + DS_HDR_SZ); + nack->svc_handle = req->svc_handle; + nack->result = DS_REG_VER_NACK; + nack->major_vers = 0; + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: reg_nack>: '%s'" DS_EOL, + PORTID(port), req->svc_id); + /* + * Send the response + */ + (void) ds_send_msg(port, msg, msglen); + DS_FREE(msg, msglen); + return; + } + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <reg_req: '%s' found, hdl: 0x%llx" DS_EOL, + PORTID(port), req->svc_id, (u_longlong_t)svc->hdl); + + /* + * A client sends out a reg req in order to force service providers to + * initiate a reg req from their end (limitation in the protocol). If a + * service provider already thinks it's talking to someone on that port, + * something's gone wrong (probably an unreg req request was dropped). + * If we see that the service is in the wrong state, reset it. + */ + + if (DS_HDL_ISCLIENT(req->svc_handle)) { + if (svc->state != DS_SVC_INACTIVE) { + (void) ds_svc_unregister(svc, port); + } + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <reg_req: '%s' pinging client" + DS_EOL, PORTID(port), req->svc_id); + (void) ds_svc_port_up(svc, port); + (void) ds_svc_register_onport(svc, port); + mutex_exit(&ds_svcs.lock); + return; + } + + /* + * Only remote service providers can initiate a registration. The + * local sevice from here must be a client service. + */ + + match = negotiate_version(svc->cap.nvers, svc->cap.vers, + req->major_vers, &new_major, &new_minor); + + /* + * Check version info. ACK only if the major numbers exactly + * match. The service entity can retry with a new minor + * based on the response sent as part of the NACK. + */ + if (match) { + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <reg_req: '%s' svc%d: state: %x " + "svc_portid: %d" DS_EOL, PORTID(port), req->svc_id, + (int)DS_HDL2IDX(svc->hdl), svc->state, + (int)(svc->port == NULL ? -1 : PORTID(svc->port))); + /* + * If the current local service is already in use and + * it's not on this port, clone it. + */ + if (svc->state != DS_SVC_INACTIVE) { + if (svc->port != NULL && port == svc->port) { + /* + * Someone probably dropped an unreg req + * somewhere. Force a local unreg. + */ + (void) ds_svc_unregister(svc, port); + } else if (!DS_HDL_ISCLIENT(svc->hdl)) { + /* + * Can't clone a non-client (service provider) + * handle. This is because old in-kernel + * service providers can't deal with multiple + * handles. + */ + goto do_reg_nack; + } else { + svc = ds_svc_clone(svc); + } + } + svc->port = port; + svc->svc_hdl = req->svc_handle; + svc->state = DS_SVC_ACTIVE; + + msglen = DS_MSG_LEN(ds_reg_ack_t); + msg = DS_MALLOC(msglen); + + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_REG_ACK; + hdr->payload_len = sizeof (ds_reg_ack_t); + + ack = (ds_reg_ack_t *)(msg + DS_HDR_SZ); + ack->svc_handle = req->svc_handle; + ack->minor_vers = MIN(new_minor, req->minor_vers); + + + if (svc->ops.ds_reg_cb) { + /* Call the registration callback */ + version.major = req->major_vers; + version.minor = ack->minor_vers; + (*svc->ops.ds_reg_cb)(svc->ops.cb_arg, &version, + svc->hdl); + } + mutex_exit(&ds_svcs.lock); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: reg_ack>: '%s' minor=0x%04X" + DS_EOL, PORTID(port), svc->cap.svc_id, + MIN(new_minor, req->minor_vers)); + } else { + mutex_exit(&ds_svcs.lock); + + msglen = DS_MSG_LEN(ds_reg_nack_t); + msg = DS_MALLOC(msglen); + + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_REG_NACK; + hdr->payload_len = sizeof (ds_reg_nack_t); + + nack = (ds_reg_nack_t *)(msg + DS_HDR_SZ); + nack->svc_handle = req->svc_handle; + nack->result = DS_REG_VER_NACK; + nack->major_vers = new_major; + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: reg_nack>: '%s' major=0x%04X" + DS_EOL, PORTID(port), svc->cap.svc_id, new_major); + } + + /* send message */ + (void) ds_send_msg(port, msg, msglen); + DS_FREE(msg, msglen); +} + +static void +ds_handle_reg_ack(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_reg_ack_t *ack; + ds_ver_t *ver; + ds_ver_t tmpver; + ds_svc_t *svc; + size_t explen = DS_MSG_LEN(ds_reg_ack_t); + + /* sanity check the incoming message */ + if (len != explen) { + cmn_err(CE_WARN, "ds@%lx: <reg_ack: invalid message " + "length (%ld), expected %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + ack = (ds_reg_ack_t *)(buf + DS_HDR_SZ); + + mutex_enter(&ds_svcs.lock); + + /* + * This searches for service based on how we generate handles + * and so only works because this is a reg ack. + */ + if (DS_HDL_ISCLIENT(ack->svc_handle) || + (svc = ds_get_svc(ack->svc_handle)) == NULL) { + cmn_err(CE_WARN, "ds@%lx: <reg_ack: invalid handle 0x%llx" + DS_EOL, PORTID(port), (u_longlong_t)ack->svc_handle); + goto done; + } + + /* make sure the message makes sense */ + if (svc->state != DS_SVC_REG_PENDING) { + cmn_err(CE_WARN, "ds@%lx: <reg_ack: invalid state (%d)" DS_EOL, + PORTID(port), svc->state); + goto done; + } + + ver = &(svc->cap.vers[svc->ver_idx]); + + /* major version has been agreed upon */ + svc->ver.major = ver->major; + + if (ack->minor_vers >= ver->minor) { + /* + * Use the minor version specified in the + * original request. + */ + svc->ver.minor = ver->minor; + } else { + /* + * Use the lower minor version returned in + * the ack. By defninition, all lower minor + * versions must be supported. + */ + svc->ver.minor = ack->minor_vers; + } + + svc->state = DS_SVC_ACTIVE; + svc->port = port; + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <reg_ack: '%s' v%d.%d ready, hdl=0x%llx" + DS_EOL, PORTID(port), svc->cap.svc_id, svc->ver.major, + svc->ver.minor, (u_longlong_t)svc->hdl); + + /* notify the client that registration is complete */ + if (svc->ops.ds_reg_cb) { + /* + * Use a temporary version structure so that + * the copy in the svc structure cannot be + * modified by the client. + */ + tmpver.major = svc->ver.major; + tmpver.minor = svc->ver.minor; + + (*svc->ops.ds_reg_cb)(svc->ops.cb_arg, &tmpver, svc->hdl); + } + +done: + mutex_exit(&ds_svcs.lock); +} + +static void +ds_try_next_port(ds_svc_t *svc, int portid) +{ + ds_port_t *port; + ds_portset_t totry; + int i; + + DS_DBG_LDC(CE_NOTE, "ds@%x %s" DS_EOL, portid, __func__); + DS_PORTSET_NOT(totry, svc->tried); + DS_PORTSET_AND(totry, svc->avail); + if (DS_PORTSET_ISNULL(totry)) + return; + + for (i = 0; i < DS_MAX_PORTS; i++, portid++) { + if (portid >= DS_MAX_PORTS) { + portid = 0; + } + + /* + * If the port is not in the available list, + * it is not a candidate for registration. + */ + if (!DS_PORT_IN_SET(totry, portid)) { + continue; + } + + port = &ds_ports[portid]; + DS_DBG_LDC(CE_NOTE, "ds@%x: %s trying ldc.id: %d" DS_EOL, + portid, __func__, (uint_t)(port->ldc.id)); + if (ds_send_reg_req(svc, port) == 0) { + DS_DBG_LDC(CE_NOTE, "ds@%x: %s reg msg send OK" DS_EOL, + portid, __func__); + /* register sent successfully */ + break; + } + DS_DBG_LDC(CE_NOTE, "ds@%x: %s reg msg send FAIL" DS_EOL, + portid, __func__); + + /* reset the service to try the next port */ + ds_reset_svc(svc, port); + } +} + +static void +ds_handle_reg_nack(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_reg_nack_t *nack; + ds_svc_t *svc; + int idx; + size_t explen = DS_MSG_LEN(ds_reg_nack_t); + + /* sanity check the incoming message */ + if (len != explen) { + cmn_err(CE_WARN, "ds@%lx: <reg_nack: invalid message " + "length (%ld), expected %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + nack = (ds_reg_nack_t *)(buf + DS_HDR_SZ); + + mutex_enter(&ds_svcs.lock); + + /* + * We expect a reg_nack for a client ping. + */ + if (DS_HDL_ISCLIENT(nack->svc_handle)) { + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <reg_nack: ping hdl: 0x%llx" + DS_EOL, PORTID(port), (u_longlong_t)nack->svc_handle); + goto done; + } + + /* + * This searches for service based on how we generate handles + * and so only works because this is a reg nack. + */ + if ((svc = ds_get_svc(nack->svc_handle)) == NULL) { + cmn_err(CE_WARN, "ds@%lx: <reg_nack: invalid handle 0x%llx" + DS_EOL, PORTID(port), (u_longlong_t)nack->svc_handle); + goto done; + } + + /* make sure the message makes sense */ + if (svc->state != DS_SVC_REG_PENDING) { + cmn_err(CE_WARN, "ds@%lx: <reg_nack: invalid state (%d)" DS_EOL, + PORTID(port), svc->state); + goto done; + } + + if (nack->result == DS_REG_DUP) { + cmn_err(CE_WARN, "ds@%lx: <reg_nack: duplicate registration " + " for %s" DS_EOL, PORTID(port), svc->cap.svc_id); + ds_reset_svc(svc, port); + goto done; + } + + /* + * A major version of zero indicates that the + * service is not supported at all. + */ + if (nack->major_vers == 0) { + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <reg_nack: '%s' not supported" + DS_EOL, PORTID(port), svc->cap.svc_id); + ds_reset_svc(svc, port); + if ((svc->flags & DSSF_ISCLIENT) == 0) + ds_try_next_port(svc, PORTID(port) + 1); + goto done; + } + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <reg_nack: '%s' hdl=0x%llx, nack=%d.x" + DS_EOL, PORTID(port), svc->cap.svc_id, + (u_longlong_t)nack->svc_handle, nack->major_vers); + + /* + * Walk the version list for the service, looking for + * a major version that is as close to the requested + * major version as possible. + */ + for (idx = svc->ver_idx; idx < svc->cap.nvers; idx++) { + if (svc->cap.vers[idx].major <= nack->major_vers) { + /* found a version to try */ + break; + } + } + + if (idx == svc->cap.nvers) { + /* no supported version */ + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <reg_nack: %s v%d.x not supported" + DS_EOL, PORTID(port), svc->cap.svc_id, nack->major_vers); + ds_reset_svc(svc, port); + if ((svc->flags & DSSF_ISCLIENT) == 0) + ds_try_next_port(svc, PORTID(port) + 1); + goto done; + } + + /* start the handshake again */ + svc->state = DS_SVC_INACTIVE; + svc->ver_idx = idx; + + (void) ds_svc_register(svc, NULL); + +done: + mutex_exit(&ds_svcs.lock); +} + +static void +ds_handle_unreg_req(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_hdr_t *hdr; + ds_unreg_req_t *req; + ds_unreg_ack_t *ack; + ds_svc_t *svc; + char *msg; + size_t msglen; + size_t explen = DS_MSG_LEN(ds_unreg_req_t); + + /* sanity check the incoming message */ + if (len != explen) { + cmn_err(CE_WARN, "ds@%lx: <unreg_req: invalid message " + "length (%ld), expected %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + req = (ds_unreg_req_t *)(buf + DS_HDR_SZ); + + mutex_enter(&ds_svcs.lock); + + /* lookup appropriate client or service */ + if (DS_HDL_ISCLIENT(req->svc_handle) || + ((svc = ds_find_clnt_svc_by_hdl_port(req->svc_handle, port)) + == NULL && ((svc = ds_get_svc(req->svc_handle)) == NULL || + svc->port != port))) { + cmn_err(CE_WARN, "ds@%lx: <unreg_req: invalid handle 0x%llx" + DS_EOL, PORTID(port), (u_longlong_t)req->svc_handle); + ds_send_unreg_nack(port, req->svc_handle); + mutex_exit(&ds_svcs.lock); + return; + } + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <unreg_req: '%s' handle 0x%llx" DS_EOL, + PORTID(port), svc->cap.svc_id, (u_longlong_t)req->svc_handle); + + (void) ds_svc_unregister(svc, svc->port); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: unreg_ack>: '%s' hdl=0x%llx" DS_EOL, + PORTID(port), svc->cap.svc_id, (u_longlong_t)req->svc_handle); + + ds_check_for_dup_services(svc); + + mutex_exit(&ds_svcs.lock); + + msglen = DS_HDR_SZ + sizeof (ds_unreg_ack_t); + msg = DS_MALLOC(msglen); + + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_UNREG_ACK; + hdr->payload_len = sizeof (ds_unreg_ack_t); + + ack = (ds_unreg_ack_t *)(msg + DS_HDR_SZ); + ack->svc_handle = req->svc_handle; + + /* send message */ + (void) ds_send_msg(port, msg, msglen); + DS_FREE(msg, msglen); + +} + +static void +ds_handle_unreg_ack(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_unreg_ack_t *ack; + size_t explen = DS_MSG_LEN(ds_unreg_ack_t); + + /* sanity check the incoming message */ + if (len != explen) { + cmn_err(CE_WARN, "ds@%lx: <unreg_ack: invalid message " + "length (%ld), expected %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + ack = (ds_unreg_ack_t *)(buf + DS_HDR_SZ); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <unreg_ack: hdl=0x%llx" DS_EOL, + PORTID(port), (u_longlong_t)ack->svc_handle); + + mutex_enter(&ds_svcs.lock); + + /* + * Since the unregister request was initiated locally, + * the service structure has already been torn down. + * Just perform a sanity check to make sure the message + * is appropriate. + */ + if (ds_get_svc(ack->svc_handle) != NULL) { + cmn_err(CE_WARN, "ds@%lx: <unreg_ack: handle 0x%llx in use" + DS_EOL, PORTID(port), (u_longlong_t)ack->svc_handle); + } + + mutex_exit(&ds_svcs.lock); +} + +static void +ds_handle_unreg_nack(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_unreg_nack_t *nack; + size_t explen = DS_MSG_LEN(ds_unreg_nack_t); + + /* sanity check the incoming message */ + if (len != explen) { + cmn_err(CE_WARN, "ds@%lx: <unreg_nack: invalid message " + "length (%ld), expected %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + nack = (ds_unreg_nack_t *)(buf + DS_HDR_SZ); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <unreg_nack: hdl=0x%llx" DS_EOL, + PORTID(port), (u_longlong_t)nack->svc_handle); + + mutex_enter(&ds_svcs.lock); + + /* + * Since the unregister request was initiated locally, + * the service structure has already been torn down. + * Just perform a sanity check to make sure the message + * is appropriate. + */ + if (ds_get_svc(nack->svc_handle) != NULL) { + cmn_err(CE_WARN, "ds@%lx: <unreg_nack: handle 0x%llx in use" + DS_EOL, PORTID(port), (u_longlong_t)nack->svc_handle); + } + + mutex_exit(&ds_svcs.lock); +} + +static void +ds_handle_data(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_data_handle_t *data; + ds_svc_t *svc; + char *msg; + int msgsz; + int hdrsz; + size_t explen = DS_MSG_LEN(ds_data_handle_t); + + /* sanity check the incoming message */ + if (len < explen) { + cmn_err(CE_WARN, "ds@%lx: <data: invalid message length " + "(%ld), expected at least %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + data = (ds_data_handle_t *)(buf + DS_HDR_SZ); + + hdrsz = DS_HDR_SZ + sizeof (ds_data_handle_t); + msgsz = len - hdrsz; + + /* strip off the header for the client */ + msg = (msgsz) ? (buf + hdrsz) : NULL; + + mutex_enter(&ds_svcs.lock); + + if ((svc = ds_find_clnt_svc_by_hdl_port(data->svc_handle, port)) + == NULL) { + if ((svc = ds_get_svc(data->svc_handle)) == NULL) { + mutex_exit(&ds_svcs.lock); + cmn_err(CE_WARN, "ds@%lx: <data: invalid handle 0x%llx" + DS_EOL, PORTID(port), + (u_longlong_t)data->svc_handle); + ds_send_data_nack(port, data->svc_handle); + return; + } + } + + mutex_exit(&ds_svcs.lock); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: <data: '%s' hdl=0x%llx" DS_EOL, + PORTID(port), svc->cap.svc_id, (u_longlong_t)svc->hdl); + DS_DUMP_MSG(DS_DBG_FLAG_PRCL, msg, msgsz); + + /* dispatch this message to the client */ + (*svc->ops.ds_data_cb)(svc->ops.cb_arg, msg, msgsz); +} + +static void +ds_handle_nack(ds_port_t *port, caddr_t buf, size_t len) +{ + ds_svc_t *svc; + ds_data_nack_t *nack; + size_t explen = DS_MSG_LEN(ds_data_nack_t); + + /* sanity check the incoming message */ + if (len != explen) { + cmn_err(CE_WARN, "ds@%lx: <data_nack: invalid message " + "length (%ld), expected %ld" DS_EOL, PORTID(port), len, + explen); + return; + } + + nack = (ds_data_nack_t *)(buf + DS_HDR_SZ); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: data_nack: hdl=0x%llx, result=0x%llx" + DS_EOL, PORTID(port), (u_longlong_t)nack->svc_handle, + (u_longlong_t)nack->result); + + if (nack->result == DS_INV_HDL) { + + mutex_enter(&ds_svcs.lock); + + if ((svc = ds_find_clnt_svc_by_hdl_port(nack->svc_handle, + port)) == NULL) { + if ((svc = ds_get_svc(nack->svc_handle)) == NULL) { + mutex_exit(&ds_svcs.lock); + return; + } + } + + cmn_err(CE_WARN, "ds@%lx: <data_nack: handle 0x%llx reported " + " as invalid" DS_EOL, PORTID(port), + (u_longlong_t)nack->svc_handle); + + (void) ds_svc_unregister(svc, svc->port); + + mutex_exit(&ds_svcs.lock); + } +} + +/* Initialize the port */ +void +ds_send_init_req(ds_port_t *port) +{ + ds_hdr_t *hdr; + ds_init_req_t *init_req; + size_t msglen; + ds_ver_t *vers = &ds_vers[port->ver_idx]; + + mutex_enter(&port->lock); + if (port->state != DS_PORT_LDC_INIT) { + DS_DBG_PRCL(CE_NOTE, "ds@%lx: init_req>: invalid state: %d" + DS_EOL, PORTID(port), port->state); + mutex_exit(&port->lock); + return; + } + mutex_exit(&port->lock); + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: init_req>: req=v%d.%d" DS_EOL, + PORTID(port), vers->major, vers->minor); + + msglen = DS_HDR_SZ + sizeof (ds_init_req_t); + hdr = DS_MALLOC(msglen); + + hdr->msg_type = DS_INIT_REQ; + hdr->payload_len = sizeof (ds_init_req_t); + + init_req = (ds_init_req_t *)((caddr_t)hdr + DS_HDR_SZ); + init_req->major_vers = vers->major; + init_req->minor_vers = vers->minor; + + if (ds_send_msg(port, (caddr_t)hdr, msglen) == 0) { + /* + * We've left the port state unlocked over the malloc/send, + * make sure no one has changed the state under us before + * we update the state. + */ + mutex_enter(&port->lock); + if (port->state == DS_PORT_LDC_INIT) + port->state = DS_PORT_INIT_REQ; + mutex_exit(&port->lock); + } + DS_FREE(hdr, msglen); +} + +static int +ds_send_reg_req(ds_svc_t *svc, ds_port_t *port) +{ + ds_ver_t *ver; + ds_hdr_t *hdr; + caddr_t msg; + size_t msglen; + ds_reg_req_t *req; + size_t idlen; + int rv; + + ASSERT(svc->state == DS_SVC_INACTIVE || + (svc->flags & DSSF_ISCLIENT) != 0); + + mutex_enter(&port->lock); + + /* check on the LDC to Zeus */ + if (port->ldc.state != LDC_UP) { + /* can not send message */ + DS_DBG_PRCL(CE_NOTE, "ds@%lx: reg_req>: channel %ld is not up" + DS_EOL, PORTID(port), port->ldc.id); + mutex_exit(&port->lock); + return (-1); + } + + /* make sure port is ready */ + if (port->state != DS_PORT_READY) { + /* can not send message */ + DS_DBG_PRCL(CE_NOTE, "ds@%lx: reg_req>: port is not ready" + DS_EOL, PORTID(port)); + mutex_exit(&port->lock); + return (-1); + } + + mutex_exit(&port->lock); + + /* allocate the message buffer */ + idlen = strlen(svc->cap.svc_id); + msglen = DS_HDR_SZ + sizeof (ds_reg_req_t) + idlen; + msg = DS_MALLOC(msglen); + + /* copy in the header data */ + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_REG_REQ; + hdr->payload_len = sizeof (ds_reg_req_t) + idlen; + + req = (ds_reg_req_t *)(msg + DS_HDR_SZ); + req->svc_handle = svc->hdl; + ver = &(svc->cap.vers[svc->ver_idx]); + req->major_vers = ver->major; + req->minor_vers = ver->minor; + + /* copy in the service id */ + (void) memcpy(req->svc_id, svc->cap.svc_id, idlen + 1); + + /* send the message */ + DS_DBG_PRCL(CE_NOTE, "ds@%lx: reg_req>: '%s' ver=%d.%d, hdl=0x%llx" + DS_EOL, PORTID(port), svc->cap.svc_id, ver->major, ver->minor, + (u_longlong_t)svc->hdl); + + if ((rv = ds_send_msg(port, msg, msglen)) != 0) { + svc->port = port; + rv = -1; + } else if ((svc->flags & DSSF_ISCLIENT) == 0) { + svc->state = DS_SVC_REG_PENDING; + } + DS_FREE(msg, msglen); + + return (rv); +} + +/* + * Keep around in case we want this later + */ +int +ds_send_unreg_req(ds_svc_t *svc) +{ + caddr_t msg; + size_t msglen; + ds_hdr_t *hdr; + ds_unreg_req_t *req; + ds_port_t *port = svc->port; + int rv; + + if (port == NULL) { + DS_DBG(CE_NOTE, "send_unreg_req: service '%s' not " + "associated with a port" DS_EOL, svc->cap.svc_id); + return (-1); + } + + mutex_enter(&port->lock); + + /* check on the LDC to Zeus */ + if (port->ldc.state != LDC_UP) { + /* can not send message */ + cmn_err(CE_WARN, "ds@%lx: unreg_req>: channel %ld is not up" + DS_EOL, PORTID(port), port->ldc.id); + mutex_exit(&port->lock); + return (-1); + } + + /* make sure port is ready */ + if (port->state != DS_PORT_READY) { + /* can not send message */ + cmn_err(CE_WARN, "ds@%lx: unreg_req>: port is not ready" DS_EOL, + PORTID(port)); + mutex_exit(&port->lock); + return (-1); + } + + mutex_exit(&port->lock); + + msglen = DS_HDR_SZ + sizeof (ds_unreg_req_t); + msg = DS_MALLOC(msglen); + + /* copy in the header data */ + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_UNREG; + hdr->payload_len = sizeof (ds_unreg_req_t); + + req = (ds_unreg_req_t *)(msg + DS_HDR_SZ); + if (svc->flags & DSSF_ISCLIENT) { + req->svc_handle = svc->svc_hdl; + } else { + req->svc_handle = svc->hdl; + } + + /* send the message */ + DS_DBG_PRCL(CE_NOTE, "ds@%lx: unreg_req>: '%s' hdl=0x%llx" DS_EOL, + PORTID(port), (svc->cap.svc_id) ? svc->cap.svc_id : "NULL", + (u_longlong_t)svc->hdl); + + if ((rv = ds_send_msg(port, msg, msglen)) != 0) { + rv = -1; + } + DS_FREE(msg, msglen); + + return (rv); +} + +static void +ds_send_unreg_nack(ds_port_t *port, ds_svc_hdl_t bad_hdl) +{ + caddr_t msg; + size_t msglen; + ds_hdr_t *hdr; + ds_unreg_nack_t *nack; + + mutex_enter(&port->lock); + + /* check on the LDC to Zeus */ + if (port->ldc.state != LDC_UP) { + /* can not send message */ + cmn_err(CE_WARN, "ds@%lx: unreg_nack>: channel %ld is not up" + DS_EOL, PORTID(port), port->ldc.id); + mutex_exit(&port->lock); + return; + } + + /* make sure port is ready */ + if (port->state != DS_PORT_READY) { + /* can not send message */ + cmn_err(CE_WARN, "ds@%lx: unreg_nack>: port is not ready" + DS_EOL, PORTID(port)); + mutex_exit(&port->lock); + return; + } + + mutex_exit(&port->lock); + + msglen = DS_HDR_SZ + sizeof (ds_unreg_nack_t); + msg = DS_MALLOC(msglen); + + /* copy in the header data */ + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_UNREG_NACK; + hdr->payload_len = sizeof (ds_unreg_nack_t); + + nack = (ds_unreg_nack_t *)(msg + DS_HDR_SZ); + nack->svc_handle = bad_hdl; + + /* send the message */ + DS_DBG_PRCL(CE_NOTE, "ds@%lx: unreg_nack>: hdl=0x%llx" DS_EOL, + PORTID(port), (u_longlong_t)bad_hdl); + + (void) ds_send_msg(port, msg, msglen); + DS_FREE(msg, msglen); +} + +static void +ds_send_data_nack(ds_port_t *port, ds_svc_hdl_t bad_hdl) +{ + caddr_t msg; + size_t msglen; + ds_hdr_t *hdr; + ds_data_nack_t *nack; + + mutex_enter(&port->lock); + + /* check on the LDC to Zeus */ + if (port->ldc.state != LDC_UP) { + /* can not send message */ + cmn_err(CE_WARN, "ds@%lx: data_nack>: channel %ld is not up" + DS_EOL, PORTID(port), port->ldc.id); + mutex_exit(&port->lock); + return; + } + + /* make sure port is ready */ + if (port->state != DS_PORT_READY) { + /* can not send message */ + cmn_err(CE_WARN, "ds@%lx: data_nack>: port is not ready" DS_EOL, + PORTID(port)); + mutex_exit(&port->lock); + return; + } + + mutex_exit(&port->lock); + + msglen = DS_HDR_SZ + sizeof (ds_data_nack_t); + msg = DS_MALLOC(msglen); + + /* copy in the header data */ + hdr = (ds_hdr_t *)msg; + hdr->msg_type = DS_NACK; + hdr->payload_len = sizeof (ds_data_nack_t); + + nack = (ds_data_nack_t *)(msg + DS_HDR_SZ); + nack->svc_handle = bad_hdl; + nack->result = DS_INV_HDL; + + /* send the message */ + DS_DBG_PRCL(CE_NOTE, "ds@%lx: data_nack>: hdl=0x%llx" DS_EOL, + PORTID(port), (u_longlong_t)bad_hdl); + + (void) ds_send_msg(port, msg, msglen); + DS_FREE(msg, msglen); +} + +/* END DS PROTOCOL SUPPORT FUNCTIONS */ + +#ifdef DEBUG + +#define BYTESPERLINE 8 +#define LINEWIDTH ((BYTESPERLINE * 3) + (BYTESPERLINE + 2) + 1) +#define ASCIIOFFSET ((BYTESPERLINE * 3) + 2) +#define ISPRINT(c) ((c >= ' ') && (c <= '~')) + +/* + * Output a buffer formatted with a set number of bytes on + * each line. Append each line with the ASCII equivalent of + * each byte if it falls within the printable ASCII range, + * and '.' otherwise. + */ +void +ds_dump_msg(void *vbuf, size_t len) +{ + int i, j; + char *curr; + char *aoff; + char line[LINEWIDTH]; + uint8_t *buf = vbuf; + + if (len > 128) + len = 128; + + /* walk the buffer one line at a time */ + for (i = 0; i < len; i += BYTESPERLINE) { + + bzero(line, LINEWIDTH); + + curr = line; + aoff = line + ASCIIOFFSET; + + /* + * Walk the bytes in the current line, storing + * the hex value for the byte as well as the + * ASCII representation in a temporary buffer. + * All ASCII values are placed at the end of + * the line. + */ + for (j = 0; (j < BYTESPERLINE) && ((i + j) < len); j++) { + (void) sprintf(curr, " %02x", buf[i + j]); + *aoff = (ISPRINT(buf[i + j])) ? buf[i + j] : '.'; + curr += 3; + aoff++; + } + + /* + * Fill in to the start of the ASCII translation + * with spaces. This will only be necessary if + * this is the last line and there are not enough + * bytes to fill the whole line. + */ + while (curr != (line + ASCIIOFFSET)) + *curr++ = ' '; + + cmn_err(CE_NOTE, "%s" DS_EOL, line); + } +} +#endif /* DEBUG */ + + +/* + * Walk the table of registered services, executing the specified callback + * function for each service on a port. A non-zero return value from the + * callback is used to terminate the walk, not to indicate an error. Returns + * the index of the last service visited. + */ +int +ds_walk_svcs(svc_cb_t svc_cb, void *arg) +{ + int idx; + ds_svc_t *svc; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + /* walk every table entry */ + for (idx = 0; idx < ds_svcs.maxsvcs; idx++) { + svc = ds_svcs.tbl[idx]; + + /* execute the callback */ + if ((*svc_cb)(svc, arg) != 0) + break; + } + + return (idx); +} + +static int +ds_svc_isfree(ds_svc_t *svc, void *arg) +{ + _NOTE(ARGUNUSED(arg)) + + /* + * Looking for a free service. This may be a NULL entry + * in the table, or an unused structure that could be + * reused. + */ + + if (DS_SVC_ISFREE(svc)) { + /* yes, it is free */ + return (1); + } + + /* not a candidate */ + return (0); +} + +int +ds_svc_ismatch(ds_svc_t *svc, void *arg) +{ + if (DS_SVC_ISFREE(svc)) { + return (0); + } + + if (strcmp(svc->cap.svc_id, arg) == 0 && + (svc->flags & DSSF_ISCLIENT) == 0) { + /* found a match */ + return (1); + } + + return (0); +} + +int +ds_svc_clnt_ismatch(ds_svc_t *svc, void *arg) +{ + if (DS_SVC_ISFREE(svc)) { + return (0); + } + + if (strcmp(svc->cap.svc_id, arg) == 0 && + (svc->flags & DSSF_ISCLIENT) != 0) { + /* found a match */ + return (1); + } + + return (0); +} + +int +ds_svc_free(ds_svc_t *svc, void *arg) +{ + _NOTE(ARGUNUSED(arg)) + + if (svc == NULL) { + return (0); + } + + if (svc->cap.svc_id) { + DS_FREE(svc->cap.svc_id, strlen(svc->cap.svc_id) + 1); + svc->cap.svc_id = NULL; + } + + if (svc->cap.vers) { + DS_FREE(svc->cap.vers, svc->cap.nvers * sizeof (ds_ver_t)); + svc->cap.vers = NULL; + } + + DS_FREE(svc, sizeof (ds_svc_t)); + + return (0); +} + +static int +ds_svc_register_onport(ds_svc_t *svc, ds_port_t *port) +{ + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + if (DS_SVC_ISFREE(svc)) + return (0); + + if (!DS_PORT_IN_SET(svc->avail, PORTID(port))) + return (0); + + DS_PORTSET_ADD(svc->tried, PORTID(port)); + + if (ds_send_reg_req(svc, port) == 0) { + /* register sent successfully */ + return (1); + } + + if ((svc->flags & DSSF_ISCLIENT) == 0) { + /* reset the service */ + ds_reset_svc(svc, port); + } + return (0); +} + +int +ds_svc_register(ds_svc_t *svc, void *arg) +{ + _NOTE(ARGUNUSED(arg)) + ds_portset_t ports; + ds_port_t *port; + int idx; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + if (DS_SVC_ISFREE(svc)) + return (0); + + ports = svc->avail; + if (svc->flags & DSSF_ISCLIENT) { + ds_portset_del_active_clients(svc->cap.svc_id, &ports); + } else if (svc->state != DS_SVC_INACTIVE) + return (0); + + if (DS_PORTSET_ISNULL(ports)) + return (0); + + /* + * Attempt to register the service. Start with the lowest + * numbered port and continue until a registration message + * is sent successfully, or there are no ports left to try. + */ + for (idx = 0; idx < DS_MAX_PORTS; idx++) { + + /* + * If the port is not in the available list, + * it is not a candidate for registration. + */ + if (!DS_PORT_IN_SET(ports, idx)) { + continue; + } + + port = &ds_ports[idx]; + if (ds_svc_register_onport(svc, port)) { + if ((svc->flags & DSSF_ISCLIENT) == 0) + break; + DS_PORTSET_DEL(svc->avail, idx); + } + } + + return (0); +} + +static int +ds_svc_unregister(ds_svc_t *svc, void *arg) +{ + ds_port_t *port = (ds_port_t *)arg; + ds_svc_hdl_t hdl; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + if (DS_SVC_ISFREE(svc)) { + return (0); + } + + /* make sure the service is using this port */ + if (svc->port != port) { + return (0); + } + + if (port) { + DS_DBG(CE_NOTE, "ds@%lx: svc_unreg: id='%s', ver=%d.%d, " + " hdl=0x%09lx" DS_EOL, PORTID(port), svc->cap.svc_id, + svc->ver.major, svc->ver.minor, svc->hdl); + } else { + DS_DBG(CE_NOTE, "port=NULL: svc_unreg: id='%s', ver=%d.%d, " + " hdl=0x%09lx" DS_EOL, svc->cap.svc_id, svc->ver.major, + svc->ver.minor, svc->hdl); + } + + /* reset the service structure */ + ds_reset_svc(svc, port); + + /* call the client unregister callback */ + if (svc->ops.ds_unreg_cb) { + (*svc->ops.ds_unreg_cb)(svc->ops.cb_arg); + } + + /* increment the count in the handle to prevent reuse */ + hdl = DS_ALLOC_HDL(DS_HDL2IDX(svc->hdl), DS_HDL2COUNT(svc->hdl)); + if (DS_HDL_ISCLIENT(svc->hdl)) { + DS_HDL_SET_ISCLIENT(hdl); + } + svc->hdl = hdl; + + if (svc->state != DS_SVC_UNREG_PENDING) { + /* try to initiate a new registration */ + (void) ds_svc_register(svc, NULL); + } + + return (0); +} + +static int +ds_svc_port_up(ds_svc_t *svc, void *arg) +{ + ds_port_t *port = (ds_port_t *)arg; + + if (DS_SVC_ISFREE(svc)) { + /* nothing to do */ + return (0); + } + + DS_PORTSET_ADD(svc->avail, port->id); + DS_PORTSET_DEL(svc->tried, port->id); + + return (0); +} + +ds_svc_t * +ds_alloc_svc(void) +{ + int idx; + uint_t newmaxsvcs; + ds_svc_t **newtbl; + ds_svc_t *newsvc; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + idx = ds_walk_svcs(ds_svc_isfree, NULL); + + if (idx != ds_svcs.maxsvcs) { + goto found; + } + + /* + * There was no free space in the table. Grow + * the table to double its current size. + */ + newmaxsvcs = ds_svcs.maxsvcs * 2; + newtbl = DS_MALLOC(newmaxsvcs * sizeof (ds_svc_t *)); + + /* copy old table data to the new table */ + (void) memcpy(newtbl, ds_svcs.tbl, + ds_svcs.maxsvcs * sizeof (ds_svc_t *)); + + /* clean up the old table */ + DS_FREE(ds_svcs.tbl, ds_svcs.maxsvcs * sizeof (ds_svc_t *)); + ds_svcs.tbl = newtbl; + ds_svcs.maxsvcs = newmaxsvcs; + + /* search for a free space again */ + idx = ds_walk_svcs(ds_svc_isfree, NULL); + + /* the table is locked so should find a free slot */ + ASSERT(idx != ds_svcs.maxsvcs); + +found: + /* allocate a new svc structure if necessary */ + if ((newsvc = ds_svcs.tbl[idx]) == NULL) { + /* allocate a new service */ + newsvc = DS_MALLOC(sizeof (ds_svc_t)); + ds_svcs.tbl[idx] = newsvc; + } + + /* fill in the handle */ + newsvc->hdl = DS_ALLOC_HDL(idx, DS_HDL2COUNT(newsvc->hdl)); + newsvc->state = DS_SVC_FREE; /* Mark as free temporarily */ + + return (newsvc); +} + +static void +ds_reset_svc(ds_svc_t *svc, ds_port_t *port) +{ + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + if (svc->state != DS_SVC_UNREG_PENDING) + svc->state = DS_SVC_INACTIVE; + svc->ver_idx = 0; + svc->ver.major = 0; + svc->ver.minor = 0; + svc->port = NULL; + if (port) { + DS_PORTSET_DEL(svc->avail, port->id); + } +} + +ds_svc_t * +ds_get_svc(ds_svc_hdl_t hdl) +{ + int idx; + ds_svc_t *svc; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + if (hdl == DS_INVALID_HDL) + return (NULL); + + idx = DS_HDL2IDX(hdl); + + /* check if index is out of bounds */ + if ((idx < 0) || (idx >= ds_svcs.maxsvcs)) + return (NULL); + + svc = ds_svcs.tbl[idx]; + + /* check for a valid service */ + if (DS_SVC_ISFREE(svc)) + return (NULL); + + /* make sure the handle is an exact match */ + if (svc->hdl != hdl) + return (NULL); + + return (svc); +} + +static void +ds_port_reset(ds_port_t *port) +{ + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + ASSERT(MUTEX_HELD(&port->lock)); + + /* connection went down, mark everything inactive */ + (void) ds_walk_svcs(ds_svc_unregister, port); + + port->ver_idx = 0; + port->ver.major = 0; + port->ver.minor = 0; + port->state = DS_PORT_LDC_INIT; +} + +/* + * Verify that a version array is sorted as expected for the + * version negotiation to work correctly. + */ +ds_vers_check_t +ds_vers_isvalid(ds_ver_t *vers, int nvers) +{ + uint16_t curr_major; + uint16_t curr_minor; + int idx; + + curr_major = vers[0].major; + curr_minor = vers[0].minor; + + /* + * Walk the version array, verifying correct ordering. + * The array must be sorted from highest supported + * version to lowest supported version. + */ + for (idx = 0; idx < nvers; idx++) { + if (vers[idx].major > curr_major) { + DS_DBG(CE_NOTE, "ds_vers_isvalid: version array has " + " increasing major versions" DS_EOL); + return (DS_VERS_INCREASING_MAJOR_ERR); + } + + if (vers[idx].major < curr_major) { + curr_major = vers[idx].major; + curr_minor = vers[idx].minor; + continue; + } + + if (vers[idx].minor > curr_minor) { + DS_DBG(CE_NOTE, "ds_vers_isvalid: version array has " + " increasing minor versions" DS_EOL); + return (DS_VERS_INCREASING_MINOR_ERR); + } + + curr_minor = vers[idx].minor; + } + + return (DS_VERS_OK); +} + +/* + * Extended user capability init. + */ +int +ds_ucap_init(ds_capability_t *cap, ds_clnt_ops_t *ops, uint32_t flags, + int instance, ds_svc_hdl_t *hdlp) +{ + ds_vers_check_t status; + ds_svc_t *svc; + int rv = 0; + ds_svc_hdl_t lb_hdl, hdl; + int is_loopback; + int is_client; + + /* sanity check the args */ + if ((cap == NULL) || (ops == NULL)) { + cmn_err(CE_NOTE, "%s: invalid arguments" DS_EOL, __func__); + return (EINVAL); + } + + /* sanity check the capability specifier */ + if ((cap->svc_id == NULL) || (cap->vers == NULL) || (cap->nvers == 0)) { + cmn_err(CE_NOTE, "%s: invalid capability specifier" DS_EOL, + __func__); + return (EINVAL); + } + + /* sanity check the version array */ + if ((status = ds_vers_isvalid(cap->vers, cap->nvers)) != DS_VERS_OK) { + cmn_err(CE_NOTE, "%s: invalid capability version array " + "for %s service: %s" DS_EOL, __func__, cap->svc_id, + (status == DS_VERS_INCREASING_MAJOR_ERR) ? + "increasing major versions" : + "increasing minor versions"); + return (EINVAL); + } + + /* data and register callbacks are required */ + if ((ops->ds_data_cb == NULL) || (ops->ds_reg_cb == NULL)) { + cmn_err(CE_NOTE, "%s: invalid ops specifier for %s service" + DS_EOL, __func__, cap->svc_id); + return (EINVAL); + } + + flags &= DSSF_USERFLAGS; + is_client = flags & DSSF_ISCLIENT; + + DS_DBG_USR(CE_NOTE, "%s: svc_id='%s', data_cb=0x%lx, cb_arg=0x%lx" + DS_EOL, __func__, cap->svc_id, PTR_TO_LONG(ops->ds_data_cb), + PTR_TO_LONG(ops->cb_arg)); + + mutex_enter(&ds_svcs.lock); + + /* check if the service is already registered */ + if (i_ds_hdl_lookup(cap->svc_id, is_client, NULL, 1) == 1) { + /* already registered */ + cmn_err(CE_NOTE, "Service '%s'/%s already registered" DS_EOL, + cap->svc_id, + (flags & DSSF_ISCLIENT) ? "client" : "service"); + mutex_exit(&ds_svcs.lock); + return (EALREADY); + } + + svc = ds_alloc_svc(); + if (is_client) { + DS_HDL_SET_ISCLIENT(svc->hdl); + } + + svc->state = DS_SVC_FREE; + svc->svc_hdl = DS_BADHDL1; + + svc->flags = flags; + svc->drvi = instance; + svc->drv_psp = NULL; + + /* + * Check for loopback. + */ + if (i_ds_hdl_lookup(cap->svc_id, is_client == 0, &lb_hdl, 1) == 1) { + if ((rv = ds_loopback_set_svc(svc, lb_hdl)) != 0) { + cmn_err(CE_WARN, "%s: ds_loopback_set_svc err (%d)" + DS_EOL, __func__, rv); + mutex_exit(&ds_svcs.lock); + return (rv); + } + is_loopback = 1; + } else + is_loopback = 0; + + /* copy over all the client information */ + (void) memcpy(&svc->cap, cap, sizeof (ds_capability_t)); + + /* make a copy of the service name */ + svc->cap.svc_id = ds_strdup(cap->svc_id); + + /* make a copy of the version array */ + svc->cap.vers = DS_MALLOC(cap->nvers * sizeof (ds_ver_t)); + (void) memcpy(svc->cap.vers, cap->vers, cap->nvers * sizeof (ds_ver_t)); + + /* copy the client ops vector */ + (void) memcpy(&svc->ops, ops, sizeof (ds_clnt_ops_t)); + + svc->state = DS_SVC_INACTIVE; + svc->ver_idx = 0; + DS_PORTSET_DUP(svc->avail, ds_allports); + DS_PORTSET_SETNULL(svc->tried); + + ds_svcs.nsvcs++; + + hdl = svc->hdl; + + /* + * kludge to allow user callback code to get handle and user args. + * Make sure the callback arg points to the svc structure. + */ + if ((flags & DSSF_ISUSER) != 0) { + ds_cbarg_set_cookie(svc); + } + + if (is_loopback) { + ds_loopback_register(hdl); + ds_loopback_register(lb_hdl); + } + + /* + * If this is a client or a non-loopback service provider, send + * out register requests. + */ + if (!is_loopback || (flags & DSSF_ISCLIENT) != 0) + (void) ds_svc_register(svc, NULL); + + if (hdlp) { + *hdlp = hdl; + } + + mutex_exit(&ds_svcs.lock); + + DS_DBG_USR(CE_NOTE, "%s: service '%s' assigned handle 0x%09lx" DS_EOL, + __func__, svc->cap.svc_id, hdl); + + return (0); +} + +/* + * ds_cap_init interface for previous revision. + */ +int +ds_cap_init(ds_capability_t *cap, ds_clnt_ops_t *ops) +{ + return (ds_ucap_init(cap, ops, 0, DS_INVALID_INSTANCE, NULL)); +} + +/* + * Interface for ds_unreg_hdl in lds driver. + */ +int +ds_unreg_hdl(ds_svc_hdl_t hdl) +{ + ds_svc_t *svc; + int is_loopback; + ds_svc_hdl_t lb_hdl; + + DS_DBG_USR(CE_NOTE, "%s: hdl=0x%09lx" DS_EOL, __func__, hdl); + + mutex_enter(&ds_svcs.lock); + if ((svc = ds_get_svc(hdl)) == NULL) { + mutex_exit(&ds_svcs.lock); + DS_DBG_USR(CE_NOTE, "%s: unknown hdl: 0x%llx" DS_EOL, __func__, + (u_longlong_t)hdl); + return (ENXIO); + } + + DS_DBG_USR(CE_NOTE, "%s: svcid='%s', hdl=0x%llx" DS_EOL, __func__, + svc->cap.svc_id, (u_longlong_t)svc->hdl); + + svc->state = DS_SVC_UNREG_PENDING; + + is_loopback = ((svc->flags & DSSF_LOOPBACK) != 0); + lb_hdl = svc->svc_hdl; + + if (svc->port) { + (void) ds_send_unreg_req(svc); + } + + (void) ds_svc_unregister(svc, svc->port); + + ds_delete_svc_entry(svc); + + if (is_loopback) { + ds_loopback_unregister(lb_hdl); + } + + mutex_exit(&ds_svcs.lock); + + return (0); +} + +int +ds_cap_fini(ds_capability_t *cap) +{ + ds_svc_hdl_t hdl; + int rv; + uint_t nhdls = 0; + + DS_DBG(CE_NOTE, "%s: '%s'" DS_EOL, __func__, cap->svc_id); + if ((rv = ds_hdl_lookup(cap->svc_id, 0, &hdl, 1, &nhdls)) != 0) { + DS_DBG(CE_NOTE, "%s: ds_hdl_lookup '%s' err (%d)" DS_EOL, + __func__, cap->svc_id, rv); + return (rv); + } + + if (nhdls == 0) { + DS_DBG(CE_NOTE, "%s: no such service '%s'" DS_EOL, + __func__, cap->svc_id); + return (ENXIO); + } + + if ((rv = ds_is_my_hdl(hdl, DS_INVALID_INSTANCE)) != 0) { + DS_DBG(CE_NOTE, "%s: ds_is_my_handle err (%d)" DS_EOL, __func__, + rv); + return (rv); + } + + if ((rv = ds_unreg_hdl(hdl)) != 0) { + DS_DBG(CE_NOTE, "%s: ds_unreg_hdl err (%d)" DS_EOL, __func__, + rv); + return (rv); + } + + return (0); +} + +int +ds_cap_send(ds_svc_hdl_t hdl, void *buf, size_t len) +{ + int rv; + ds_hdr_t *hdr; + caddr_t msg; + size_t msglen; + size_t hdrlen; + caddr_t payload; + ds_svc_t *svc; + ds_port_t *port; + ds_data_handle_t *data; + ds_svc_hdl_t svc_hdl; + int is_client = 0; + + DS_DBG(CE_NOTE, "%s: hdl: 0x%llx, buf: %lx, len: %ld" DS_EOL, __func__, + (u_longlong_t)hdl, (ulong_t)buf, len); + + mutex_enter(&ds_svcs.lock); + + if ((svc = ds_get_svc(hdl)) == NULL) { + cmn_err(CE_WARN, "%s: invalid handle 0x%llx" DS_EOL, __func__, + (u_longlong_t)hdl); + mutex_exit(&ds_svcs.lock); + return (ENXIO); + } + + if (svc->state != DS_SVC_ACTIVE) { + /* channel is up, but svc is not registered */ + DS_DBG(CE_NOTE, "%s: invalid service state 0x%x" DS_EOL, + __func__, svc->state); + mutex_exit(&ds_svcs.lock); + return (ENOTCONN); + } + + if (svc->flags & DSSF_LOOPBACK) { + hdl = svc->svc_hdl; + mutex_exit(&ds_svcs.lock); + ds_loopback_send(hdl, buf, len); + return (0); + } + + if ((port = svc->port) == NULL) { + DS_DBG(CE_NOTE, "%s: service '%s' not associated with a port" + DS_EOL, __func__, svc->cap.svc_id); + mutex_exit(&ds_svcs.lock); + return (ECONNRESET); + } + + if (svc->flags & DSSF_ISCLIENT) { + is_client = 1; + svc_hdl = svc->svc_hdl; + } + + mutex_exit(&ds_svcs.lock); + + /* check that the LDC channel is ready */ + if (port->ldc.state != LDC_UP) { + DS_DBG(CE_NOTE, "%s: LDC channel is not up" DS_EOL, __func__); + return (ECONNRESET); + } + + hdrlen = DS_HDR_SZ + sizeof (ds_data_handle_t); + + msg = DS_MALLOC(len + hdrlen); + hdr = (ds_hdr_t *)msg; + payload = msg + hdrlen; + msglen = len + hdrlen; + + hdr->payload_len = len + sizeof (ds_data_handle_t); + hdr->msg_type = DS_DATA; + + data = (ds_data_handle_t *)(msg + DS_HDR_SZ); + if (is_client) { + data->svc_handle = svc_hdl; + } else { + data->svc_handle = hdl; + } + + if ((buf != NULL) && (len != 0)) { + (void) memcpy(payload, buf, len); + } + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: data>: hdl=0x%llx, len=%ld, " + " payload_len=%d" DS_EOL, PORTID(port), (u_longlong_t)svc->hdl, + msglen, hdr->payload_len); + DS_DUMP_MSG(DS_DBG_FLAG_PRCL, msg, msglen); + + if ((rv = ds_send_msg(port, msg, msglen)) != 0) { + rv = (rv == EIO) ? ECONNRESET : rv; + } + DS_FREE(msg, msglen); + + return (rv); +} + +void +ds_port_common_init(ds_port_t *port) +{ + int rv; + + if ((port->flags & DS_PORT_MUTEX_INITED) == 0) { + mutex_init(&port->lock, NULL, MUTEX_DRIVER, NULL); + mutex_init(&port->tx_lock, NULL, MUTEX_DRIVER, NULL); + mutex_init(&port->rcv_lock, NULL, MUTEX_DRIVER, NULL); + port->flags |= DS_PORT_MUTEX_INITED; + } + + port->state = DS_PORT_INIT; + DS_PORTSET_ADD(ds_allports, port->id); + + ds_sys_port_init(port); + + mutex_enter(&port->lock); + rv = ds_ldc_init(port); + mutex_exit(&port->lock); + + /* + * If LDC successfully init'ed, try to kick off protocol for this port. + */ + if (rv == 0) { + ds_handle_up_event(port); + } +} + +void +ds_port_common_fini(ds_port_t *port, int is_fini) +{ + port->state = DS_PORT_FREE; + + if (is_fini && (port->flags & DS_PORT_MUTEX_INITED) != 0) { + mutex_destroy(&port->lock); + mutex_destroy(&port->tx_lock); + mutex_destroy(&port->rcv_lock); + port->flags &= ~DS_PORT_MUTEX_INITED; + } + + DS_PORTSET_DEL(ds_allports, port->id); + + ds_sys_port_fini(port); +} + +/* + * Initialize table of registered service classes + */ +void +ds_init_svcs_tbl(uint_t nentries) +{ + int tblsz; + + ds_svcs.maxsvcs = nentries; + + tblsz = ds_svcs.maxsvcs * sizeof (ds_svc_t *); + ds_svcs.tbl = (ds_svc_t **)DS_MALLOC(tblsz); + + ds_svcs.nsvcs = 0; +} + +/* + * Find the max and min version supported. + * Hacked from zeus workspace, support.c + */ +static void +min_max_versions(int num_versions, ds_ver_t *sup_versionsp, + uint16_t *min_major, uint16_t *max_major) +{ + int i; + + *min_major = sup_versionsp[0].major; + *max_major = *min_major; + + for (i = 1; i < num_versions; i++) { + if (sup_versionsp[i].major < *min_major) + *min_major = sup_versionsp[i].major; + + if (sup_versionsp[i].major > *max_major) + *max_major = sup_versionsp[i].major; + } +} + +/* + * Check whether the major and minor numbers requested by the peer can be + * satisfied. If the requested major is supported, true is returned, and the + * agreed minor is returned in new_minor. If the requested major is not + * supported, the routine returns false, and the closest major is returned in + * *new_major, upon which the peer should re-negotiate. The closest major is + * the just lower that the requested major number. + * + * Hacked from zeus workspace, support.c + */ +boolean_t +negotiate_version(int num_versions, ds_ver_t *sup_versionsp, + uint16_t req_major, uint16_t *new_majorp, uint16_t *new_minorp) +{ + int i; + uint16_t major, lower_major; + uint16_t min_major = 0, max_major; + boolean_t found_match = B_FALSE; + + min_max_versions(num_versions, sup_versionsp, &min_major, &max_major); + + DS_DBG(CE_NOTE, "negotiate_version: req_major = %u, min = %u, max = %u" + DS_EOL, req_major, min_major, max_major); + + /* + * If the minimum version supported is greater than + * the version requested, return the lowest version + * supported + */ + if (min_major > req_major) { + *new_majorp = min_major; + return (B_FALSE); + } + + /* + * If the largest version supported is lower than + * the version requested, return the largest version + * supported + */ + if (max_major < req_major) { + *new_majorp = max_major; + return (B_FALSE); + } + + /* + * Now we know that the requested version lies between the + * min and max versions supported. Check if the requested + * major can be found in supported versions. + */ + lower_major = min_major; + for (i = 0; i < num_versions; i++) { + major = sup_versionsp[i].major; + if (major == req_major) { + found_match = B_TRUE; + *new_majorp = req_major; + *new_minorp = sup_versionsp[i].minor; + break; + } else { + if ((major < req_major) && (major > lower_major)) + lower_major = major; + } + } + + /* + * If no match is found, return the closest available number + */ + if (!found_match) + *new_majorp = lower_major; + + return (found_match); +} + +/* + * Specific errno's that are used by ds.c and ldc.c + */ +static struct { + int ds_errno; + char *estr; +} ds_errno_to_str_tab[] = { + { EIO, "I/O error" }, + { ENXIO, "No such device or address" }, + { EAGAIN, "Resource temporarily unavailable" }, + { ENOMEM, "Not enough space" }, + { EACCES, "Permission denied" }, + { EFAULT, "Bad address" }, + { EBUSY, "Device busy" }, + { EINVAL, "Invalid argument" }, + { ENOSPC, "No space left on device" }, + { ENOMSG, "No message of desired type" }, +#ifdef ECHRNG + { ECHRNG, "Channel number out of range" }, +#endif + { ENOTSUP, "Operation not supported" }, + { EMSGSIZE, "Message too long" }, + { EADDRINUSE, "Address already in use" }, + { ECONNRESET, "Connection reset by peer" }, + { ENOBUFS, "No buffer space available" }, + { ENOTCONN, "Socket is not connected" }, + { ECONNREFUSED, "Connection refused" }, + { EALREADY, "Operation already in progress" }, + { 0, NULL }, +}; + +char * +ds_errno_to_str(int ds_errno, char *ebuf) +{ + int i, en; + + for (i = 0; (en = ds_errno_to_str_tab[i].ds_errno) != 0; i++) { + if (en == ds_errno) { + (void) strcpy(ebuf, ds_errno_to_str_tab[i].estr); + return (ebuf); + } + } + + (void) sprintf(ebuf, "ds_errno (%d)", ds_errno); + return (ebuf); +} + +static void +ds_loopback_register(ds_svc_hdl_t hdl) +{ + ds_ver_t ds_ver = { 1, 0}; + ds_svc_t *svc; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + DS_DBG_LOOP(CE_NOTE, "%s: entered hdl: 0x%llx" DS_EOL, __func__, + (u_longlong_t)hdl); + if ((svc = ds_get_svc(hdl)) == NULL) { + DS_DBG_LOOP(CE_NOTE, "%s: invalid hdl: 0x%llx" DS_EOL, __func__, + (u_longlong_t)hdl); + return; + } + svc->state = DS_SVC_ACTIVE; + + if (svc->ops.ds_reg_cb) { + DS_DBG_LOOP(CE_NOTE, "%s: loopback regcb: hdl: 0x%llx" DS_EOL, + __func__, (u_longlong_t)hdl); + ds_ver.major = svc->ver.major; + ds_ver.minor = svc->ver.minor; + (*svc->ops.ds_reg_cb)(svc->ops.cb_arg, &ds_ver, hdl); + } +} + +static void +ds_loopback_unregister(ds_svc_hdl_t hdl) +{ + ds_svc_t *svc; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + if ((svc = ds_get_svc(hdl)) == NULL) { + DS_DBG_LOOP(CE_NOTE, "%s: invalid hdl: 0x%llx" DS_EOL, __func__, + (u_longlong_t)hdl); + return; + } + + DS_DBG_LOOP(CE_NOTE, "%s: entered hdl: 0x%llx" DS_EOL, __func__, + (u_longlong_t)hdl); + + svc->flags &= ~DSSF_LOOPBACK; + svc->svc_hdl = DS_BADHDL2; + + if (svc->ops.ds_unreg_cb) { + DS_DBG_LOOP(CE_NOTE, "%s: loopback unregcb: hdl: 0x%llx" DS_EOL, + __func__, (u_longlong_t)hdl); + (*svc->ops.ds_unreg_cb)(svc->ops.cb_arg); + } +} + +static void +ds_loopback_send(ds_svc_hdl_t hdl, void *buf, size_t buflen) +{ + ds_svc_t *svc; + + mutex_enter(&ds_svcs.lock); + if ((svc = ds_get_svc(hdl)) == NULL) { + mutex_exit(&ds_svcs.lock); + DS_DBG_LOOP(CE_NOTE, "%s: invalid hdl: 0x%llx" DS_EOL, __func__, + (u_longlong_t)hdl); + return; + } + mutex_exit(&ds_svcs.lock); + + DS_DBG_LOOP(CE_NOTE, "%s: entered hdl: 0x%llx" DS_EOL, __func__, + (u_longlong_t)hdl); + + if (svc->ops.ds_data_cb) { + DS_DBG_LOOP(CE_NOTE, "%s: loopback datacb hdl: 0x%llx" DS_EOL, + __func__, (u_longlong_t)hdl); + (*svc->ops.ds_data_cb)(svc->ops.cb_arg, buf, buflen); + } +} + +static int +ds_loopback_set_svc(ds_svc_t *svc, ds_svc_hdl_t lb_hdl) +{ + ds_svc_t *lb_svc; + + if ((lb_svc = ds_get_svc(lb_hdl)) == NULL) { + DS_DBG_LOOP(CE_NOTE, "%s: loopback: hdl: 0x%llx invalid" DS_EOL, + __func__, (u_longlong_t)lb_hdl); + return (ENXIO); + } + if (lb_svc->state != DS_SVC_INACTIVE) { + DS_DBG_LOOP(CE_NOTE, "%s: loopback inactive: hdl: 0x%llx" + DS_EOL, __func__, (u_longlong_t)lb_hdl); + if ((lb_svc->flags & DSSF_ISCLIENT) == 0) { + DS_DBG_LOOP(CE_NOTE, "%s: loopback busy hdl: 0x%llx" + DS_EOL, __func__, (u_longlong_t)lb_hdl); + return (EBUSY); + } + lb_svc = ds_svc_clone(lb_svc); + DS_DBG_LOOP(CE_NOTE, "%s: loopback clone: ohdl: 0x%llx " + "nhdl: 0x%llx" DS_EOL, __func__, (u_longlong_t)lb_hdl, + (u_longlong_t)lb_svc->hdl); + } + + svc->flags |= DSSF_LOOPBACK; + svc->svc_hdl = lb_svc->hdl; + svc->port = NULL; + + lb_svc->flags |= DSSF_LOOPBACK; + lb_svc->svc_hdl = svc->hdl; + lb_svc->port = NULL; + + DS_DBG_LOOP(CE_NOTE, "%s: setting loopback between: 0x%llx and 0x%llx" + DS_EOL, __func__, (u_longlong_t)svc->hdl, + (u_longlong_t)lb_svc->hdl); + return (0); +} + +static ds_svc_t * +ds_find_clnt_svc_by_hdl_port(ds_svc_hdl_t hdl, ds_port_t *port) +{ + int idx; + ds_svc_t *svc; + + DS_DBG_PRCL(CE_NOTE, "ds@%lx: %s looking up clnt hdl: 0x%llx" DS_EOL, + PORTID(port), __func__, (u_longlong_t)hdl); + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + /* walk every table entry */ + for (idx = 0; idx < ds_svcs.maxsvcs; idx++) { + svc = ds_svcs.tbl[idx]; + if (DS_SVC_ISFREE(svc)) + continue; + if ((svc->flags & DSSF_ISCLIENT) != 0 && + svc->svc_hdl == hdl && svc->port == port) { + DS_DBG_PRCL(CE_NOTE, "ds@%lx: %s found clnt hdl " + "0x%llx: svc%d" DS_EOL, PORTID(port), __func__, + (u_longlong_t)hdl, (uint_t)DS_HDL2IDX(svc->hdl)); + return (svc); + } + } + DS_DBG_PRCL(CE_NOTE, "ds@%lx: %s clnt hdl: 0x%llx not found" DS_EOL, + PORTID(port), __func__, (u_longlong_t)hdl); + + return (NULL); +} + +static ds_svc_t * +ds_svc_clone(ds_svc_t *svc) +{ + ds_svc_t *newsvc; + ds_svc_hdl_t hdl; + + ASSERT(svc->flags & DSSF_ISCLIENT); + + newsvc = ds_alloc_svc(); + + /* Can only clone clients for now */ + hdl = newsvc->hdl | DS_HDL_ISCLIENT_BIT; + DS_DBG_USR(CE_NOTE, "%s: cloning client: old hdl: 0x%llx new hdl: " + "0x%llx" DS_EOL, __func__, (u_longlong_t)svc->hdl, + (u_longlong_t)hdl); + (void) memcpy(newsvc, svc, sizeof (ds_svc_t)); + newsvc->hdl = hdl; + newsvc->flags &= ~DSSF_LOOPBACK; + newsvc->port = NULL; + newsvc->svc_hdl = DS_BADHDL2; + newsvc->cap.svc_id = ds_strdup(svc->cap.svc_id); + newsvc->cap.vers = DS_MALLOC(svc->cap.nvers * sizeof (ds_ver_t)); + (void) memcpy(newsvc->cap.vers, svc->cap.vers, + svc->cap.nvers * sizeof (ds_ver_t)); + + /* + * Kludge to allow lds driver user callbacks to get access to current + * svc structure. Arg could be index to svc table or some other piece + * of info to get to the svc table entry. + */ + if (newsvc->flags & DSSF_ISUSER) { + newsvc->ops.cb_arg = (ds_cb_arg_t)(newsvc); + } + return (newsvc); +} + +/* + * Internal handle lookup function. + */ +static int +i_ds_hdl_lookup(char *service, uint_t is_client, ds_svc_hdl_t *hdlp, + uint_t maxhdls) +{ + int idx; + int nhdls = 0; + ds_svc_t *svc; + uint32_t client_flag = is_client ? DSSF_ISCLIENT : 0; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + for (idx = 0; idx < ds_svcs.maxsvcs && nhdls < maxhdls; idx++) { + svc = ds_svcs.tbl[idx]; + if (DS_SVC_ISFREE(svc)) + continue; + if (strcmp(svc->cap.svc_id, service) == 0 && + (svc->flags & DSSF_ISCLIENT) == client_flag) { + if (hdlp != NULL && nhdls < maxhdls) { + hdlp[nhdls] = svc->hdl; + nhdls++; + } else { + nhdls++; + } + } + } + return (nhdls); +} + +/* + * Interface for ds_hdl_lookup in lds driver. + */ +int +ds_hdl_lookup(char *service, uint_t is_client, ds_svc_hdl_t *hdlp, + uint_t maxhdls, uint_t *nhdlsp) +{ + mutex_enter(&ds_svcs.lock); + *nhdlsp = i_ds_hdl_lookup(service, is_client, hdlp, maxhdls); + mutex_exit(&ds_svcs.lock); + return (0); +} + +static void +ds_portset_del_active_clients(char *service, ds_portset_t *portsp) +{ + ds_portset_t ports = *portsp; + int idx; + ds_svc_t *svc; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + for (idx = 0; idx < ds_svcs.maxsvcs; idx++) { + svc = ds_svcs.tbl[idx]; + if (DS_SVC_ISFREE(svc)) + continue; + if (strcmp(svc->cap.svc_id, service) == 0 && + (svc->flags & DSSF_ISCLIENT) != 0 && + svc->state != DS_SVC_INACTIVE && + svc->port != NULL) { + DS_PORTSET_DEL(ports, PORTID(svc->port)); + } + } + *portsp = ports; +} + +/* + * After an UNREG REQ, check if this is a client service with multiple + * handles. If it is, then we can eliminate this entry. + */ +static void +ds_check_for_dup_services(ds_svc_t *svc) +{ + if ((svc->flags & DSSF_ISCLIENT) != 0 && + svc->state == DS_SVC_INACTIVE && + i_ds_hdl_lookup(svc->cap.svc_id, 1, NULL, 2) == 2) { + ds_delete_svc_entry(svc); + } +} + +static void +ds_delete_svc_entry(ds_svc_t *svc) +{ + ds_svc_hdl_t tmp_hdl; + + ASSERT(MUTEX_HELD(&ds_svcs.lock)); + + /* + * Clear out the structure, but do not deallocate the + * memory. It can be reused for the next registration. + */ + DS_FREE(svc->cap.svc_id, strlen(svc->cap.svc_id) + 1); + DS_FREE(svc->cap.vers, svc->cap.nvers * sizeof (ds_ver_t)); + + /* save the handle to prevent reuse */ + tmp_hdl = svc->hdl; + bzero((void *)svc, sizeof (ds_svc_t)); + + /* initialize for next use */ + svc->hdl = tmp_hdl; + svc->state = DS_SVC_FREE; + + ds_svcs.nsvcs--; +} diff --git a/usr/src/uts/sun4v/io/ds_drv.c b/usr/src/uts/sun4v/io/ds_drv.c new file mode 100644 index 0000000000..d4ffeb6fc6 --- /dev/null +++ b/usr/src/uts/sun4v/io/ds_drv.c @@ -0,0 +1,1098 @@ +/* + * 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. + */ + + +/* + * Domain Services Module System Specific Code. + * + * The Domain Services (DS) module is responsible for communication + * with external service entities. It provides a kernel API for clients to + * publish capabilities and handles the low level communication and + * version negotiation required to export those capabilities to any + * interested service entity. Once a capability has been successfully + * registered with a service entity, the DS module facilitates all + * data transfers between the service entity and the client providing + * that particular capability. + * + * This file provides the system interfaces that are required for + * the ds.c module, which is common to both Solaris and VBSC (linux). + */ + +#include <sys/modctl.h> +#include <sys/ksynch.h> +#include <sys/taskq.h> +#include <sys/disp.h> +#include <sys/cmn_err.h> +#include <sys/note.h> +#include <sys/mach_descrip.h> +#include <sys/mdesc.h> +#include <sys/mdeg.h> +#include <sys/ldc.h> +#include <sys/ds.h> +#include <sys/ds_impl.h> + +/* + * All DS ports in the system + * + * The list of DS ports is read in from the MD when the DS module is + * initialized and is never modified. This eliminates the need for + * locking to access the port array itself. Access to the individual + * ports are synchronized at the port level. + */ +ds_port_t ds_ports[DS_MAX_PORTS]; +ds_portset_t ds_allports; /* all DS ports in the system */ + +/* + * Table of registered services + * + * Locking: Accesses to the table of services are synchronized using + * a mutex lock. The reader lock must be held when looking up service + * information in the table. The writer lock must be held when any + * service information is being modified. + */ +ds_svcs_t ds_svcs; + +/* + * Taskq for internal task processing + */ +static taskq_t *ds_taskq; + +/* + * The actual required number of parallel threads is not expected + * to be very large. Use the maximum number of CPUs in the system + * as a rough upper bound. + */ +#define DS_MAX_TASKQ_THR NCPU +#define DS_DISPATCH(fn, arg) taskq_dispatch(ds_taskq, fn, arg, TQ_SLEEP) + +ds_domain_hdl_t ds_my_domain_hdl = NULL; + +#ifdef DEBUG +/* + * Debug Flag + */ +uint_t ds_debug = 0; +#endif /* DEBUG */ + +/* initialization functions */ +static void ds_init(void); +static void ds_fini(void); +static int ds_ports_init(void); +static int ds_ports_fini(void); + +/* port utilities */ +static int ds_port_add(md_t *mdp, mde_cookie_t port, mde_cookie_t chan); + +/* log functions */ +static void ds_log_init(void); +static void ds_log_fini(void); +static int ds_log_remove(void); +static void ds_log_purge(void *arg); + +static struct modlmisc modlmisc = { + &mod_miscops, + "Domain Services 1.9" +}; + +static struct modlinkage modlinkage = { + MODREV_1, + (void *)&modlmisc, + NULL +}; + +int +_init(void) +{ + int rv; + + /* + * Perform all internal setup before initializing + * the DS ports. This ensures that events can be + * processed as soon as the port comes up. + */ + ds_init(); + + /* force attach channel nexus */ + (void) i_ddi_attach_hw_nodes("cnex"); + + if ((rv = ds_ports_init()) != 0) { + cmn_err(CE_WARN, "Domain Services initialization failed"); + ds_fini(); + return (rv); + } + + if ((rv = mod_install(&modlinkage)) != 0) { + (void) ds_ports_fini(); + ds_fini(); + } + + return (rv); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +int +_fini(void) +{ + int rv; + + if ((rv = mod_remove(&modlinkage)) == 0) { + (void) ds_ports_fini(); + ds_fini(); + } + + return (rv); +} + +static void +ds_fini(void) +{ + /* + * Flip the enabled switch to make sure that no + * incoming events get dispatched while things + * are being torn down. + */ + ds_enabled = B_FALSE; + + /* + * Destroy the taskq. + */ + taskq_destroy(ds_taskq); + + /* + * Destroy the message log. + */ + ds_log_fini(); + + /* + * Deallocate the table of registered services + */ + + /* clear out all entries */ + mutex_enter(&ds_svcs.lock); + (void) ds_walk_svcs(ds_svc_free, NULL); + mutex_exit(&ds_svcs.lock); + + /* destroy the table itself */ + DS_FREE(ds_svcs.tbl, ds_svcs.maxsvcs * sizeof (ds_svc_t *)); + mutex_destroy(&ds_svcs.lock); + bzero(&ds_svcs, sizeof (ds_svcs)); +} + +/* + * Initialize the list of ports based on the MD. + */ +static int +ds_ports_init(void) +{ + int idx; + int rv = 0; + md_t *mdp; + int num_nodes; + int listsz; + mde_cookie_t rootnode; + mde_cookie_t dsnode; + mde_cookie_t *portp = NULL; + mde_cookie_t *chanp = NULL; + int nport; + int nchan; + + if ((mdp = md_get_handle()) == NULL) { + cmn_err(CE_WARN, "Unable to initialize machine description"); + return (-1); + } + + num_nodes = md_node_count(mdp); + ASSERT(num_nodes > 0); + + listsz = num_nodes * sizeof (mde_cookie_t); + + /* allocate temporary storage for MD scans */ + portp = kmem_zalloc(listsz, KM_SLEEP); + chanp = kmem_zalloc(listsz, KM_SLEEP); + + rootnode = md_root_node(mdp); + ASSERT(rootnode != MDE_INVAL_ELEM_COOKIE); + + /* + * The root of the search for DS port nodes is the + * DS node. Perform a scan to find that node. + */ + nport = md_scan_dag(mdp, rootnode, md_find_name(mdp, DS_MD_ROOT_NAME), + md_find_name(mdp, "fwd"), portp); + + if (nport <= 0) { + DS_DBG_MD(CE_NOTE, "No '%s' node in MD", DS_MD_ROOT_NAME); + goto done; + } + + /* expecting only one DS node */ + if (nport != 1) { + DS_DBG_MD(CE_NOTE, "Expected one '%s' node in the MD, found %d", + DS_MD_ROOT_NAME, nport); + } + + dsnode = portp[0]; + + /* find all the DS ports in the MD */ + nport = md_scan_dag(mdp, dsnode, md_find_name(mdp, DS_MD_PORT_NAME), + md_find_name(mdp, "fwd"), portp); + + if (nport <= 0) { + DS_DBG_MD(CE_NOTE, "No '%s' nodes in MD", DS_MD_PORT_NAME); + goto done; + } + + /* + * Initialize all the ports found in the MD. + */ + for (idx = 0; idx < nport; idx++) { + + /* get the channels for this port */ + nchan = md_scan_dag(mdp, portp[idx], + md_find_name(mdp, DS_MD_CHAN_NAME), + md_find_name(mdp, "fwd"), chanp); + + if (nchan <= 0) { + cmn_err(CE_WARN, "No '%s' node for DS port", + DS_MD_CHAN_NAME); + rv = -1; + goto done; + } + + /* expecting only one channel */ + if (nchan != 1) { + DS_DBG_MD(CE_NOTE, "Expected one '%s' node for DS " + " port, found %d", DS_MD_CHAN_NAME, nchan); + } + + if (ds_port_add(mdp, portp[idx], chanp[0]) != 0) { + rv = -1; + goto done; + } + } + +done: + if (rv != 0) + (void) ds_ports_fini(); + + DS_FREE(portp, listsz); + DS_FREE(chanp, listsz); + + (void) md_fini_handle(mdp); + + return (rv); +} + +static int +ds_ports_fini(void) +{ + int idx; + + /* + * Tear down each initialized port. + */ + for (idx = 0; idx < DS_MAX_PORTS; idx++) { + if (DS_PORT_IN_SET(ds_allports, idx)) { + (void) ds_remove_port(idx, 1); + } + } + + return (0); +} + +static int +ds_port_add(md_t *mdp, mde_cookie_t port, mde_cookie_t chan) +{ + uint64_t port_id; + uint64_t ldc_id; + uint64_t dhdl; + char *dom_name; + + /* get the ID for this port */ + if (md_get_prop_val(mdp, port, "id", &port_id) != 0) { + cmn_err(CE_WARN, "%s: port 'id' property not found", + __func__); + return (-1); + } + + /* sanity check the port id */ + if (port_id > DS_MAX_PORT_ID) { + cmn_err(CE_WARN, "%s: port ID %ld out of range", + __func__, port_id); + return (-1); + } + + /* get the channel ID for this port */ + if (md_get_prop_val(mdp, chan, "id", &ldc_id) != 0) { + cmn_err(CE_WARN, "ds@%lx: %s: no channel 'id' property", + port_id, __func__); + return (-1); + } + + /* get the remote-domain-id property if it's there */ + if (md_get_prop_val(mdp, port, "remote-domain-id", &dhdl) != 0) { + DS_DBG_MD(CE_NOTE, "ds@%lx: %s: 'remote-domain-id' prop " + " not found", port_id, __func__); + dhdl = DS_DHDL_INVALID; + } + + /* get the remote-domain-name property if it's there */ + if (md_get_prop_str(mdp, port, "remote-domain-name", &dom_name) != 0) { + DS_DBG_MD(CE_NOTE, "ds@%lx: %s: 'remote-domain-name' prop " + " not found", port_id, __func__); + dom_name = NULL; + } + + if (ds_add_port(port_id, ldc_id, dhdl, dom_name, 1) != 0) + return (-1); + + return (0); +} + +void +ds_init() +{ + ds_common_init(); + + /* + * Create taskq for internal processing threads. This + * includes processing incoming request messages and + * sending out of band registration messages. + */ + ds_taskq = taskq_create("ds_taskq", 1, minclsyspri, 1, + DS_MAX_TASKQ_THR, TASKQ_PREPOPULATE | TASKQ_DYNAMIC); + + /* + * Initialize the message log. + */ + ds_log_init(); +} + +int +ds_sys_dispatch_func(void (func)(void *), void *arg) +{ + return (DS_DISPATCH(func, arg) == NULL); +} + +/* + * Drain event queue, if necessary. + */ +void +ds_sys_drain_events(ds_port_t *port) +{ + _NOTE(ARGUNUSED(port)) +} + +/* + * System specific port initalization. + */ +void +ds_sys_port_init(ds_port_t *port) +{ + _NOTE(ARGUNUSED(port)) +} + +/* + * System specific port teardown. + */ +void +ds_sys_port_fini(ds_port_t *port) +{ + _NOTE(ARGUNUSED(port)) +} + +/* + * System specific LDC channel initialization. + */ +void +ds_sys_ldc_init(ds_port_t *port) +{ + int rv; + char ebuf[DS_EBUFSIZE]; + + ASSERT(MUTEX_HELD(&port->lock)); + + if ((rv = ldc_open(port->ldc.hdl)) != 0) { + cmn_err(CE_WARN, "ds@%lx: %s: ldc_open: %s", + PORTID(port), __func__, ds_errno_to_str(rv, ebuf)); + return; + } + + (void) ldc_up(port->ldc.hdl); + + (void) ldc_status(port->ldc.hdl, &port->ldc.state); + + DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: initial LDC state 0x%x", + PORTID(port), __func__, port->ldc.state); + + port->state = DS_PORT_LDC_INIT; +} + +/* + * DS message log + * + * Locking: The message log is protected by a single mutex. This + * protects all fields in the log structure itself as well as + * everything in the entry structures on both the log and the + * free list. + */ +static struct log { + ds_log_entry_t *head; /* head of the log */ + ds_log_entry_t *freelist; /* head of the free list */ + size_t size; /* size of the log in bytes */ + uint32_t nentry; /* number of entries */ + kmutex_t lock; /* log lock */ +} ds_log; + +/* log soft limit */ +uint_t ds_log_sz = DS_LOG_DEFAULT_SZ; + +/* initial pool of log entry structures */ +static ds_log_entry_t ds_log_entry_pool[DS_LOG_NPOOL]; + +/* + * Logging Support + */ +static void +ds_log_init(void) +{ + ds_log_entry_t *new; + + /* initialize global lock */ + mutex_init(&ds_log.lock, NULL, MUTEX_DRIVER, NULL); + + mutex_enter(&ds_log.lock); + + /* initialize the log */ + ds_log.head = NULL; + ds_log.size = 0; + ds_log.nentry = 0; + + /* initialize the free list */ + for (new = ds_log_entry_pool; new < DS_LOG_POOL_END; new++) { + new->next = ds_log.freelist; + ds_log.freelist = new; + } + + mutex_exit(&ds_log.lock); + + DS_DBG_LOG(CE_NOTE, "ds_log initialized: size=%d bytes, " + " limit=%d bytes, ninit=%ld", ds_log_sz, DS_LOG_LIMIT, + DS_LOG_NPOOL); +} + +static void +ds_log_fini(void) +{ + ds_log_entry_t *next; + + mutex_enter(&ds_log.lock); + + /* clear out the log */ + while (ds_log.nentry > 0) + (void) ds_log_remove(); + + /* + * Now all the entries are on the free list. + * Clear out the free list, deallocating any + * entry that was dynamically allocated. + */ + while (ds_log.freelist != NULL) { + next = ds_log.freelist->next; + + if (!DS_IS_POOL_ENTRY(ds_log.freelist)) { + kmem_free(ds_log.freelist, sizeof (ds_log_entry_t)); + } + + ds_log.freelist = next; + } + + mutex_exit(&ds_log.lock); + + mutex_destroy(&ds_log.lock); +} + +static ds_log_entry_t * +ds_log_entry_alloc(void) +{ + ds_log_entry_t *new = NULL; + + ASSERT(MUTEX_HELD(&ds_log.lock)); + + if (ds_log.freelist != NULL) { + new = ds_log.freelist; + ds_log.freelist = ds_log.freelist->next; + } + + if (new == NULL) { + /* free list was empty */ + new = kmem_zalloc(sizeof (ds_log_entry_t), KM_SLEEP); + } + + ASSERT(new); + + return (new); +} + +static void +ds_log_entry_free(ds_log_entry_t *entry) +{ + ASSERT(MUTEX_HELD(&ds_log.lock)); + + if (entry == NULL) + return; + + if (entry->data != NULL) { + kmem_free(entry->data, entry->datasz); + entry->data = NULL; + } + + /* place entry on the free list */ + entry->next = ds_log.freelist; + ds_log.freelist = entry; +} + +/* + * Add a message to the end of the log + */ +static int +ds_log_add(ds_log_entry_t *new) +{ + ASSERT(MUTEX_HELD(&ds_log.lock)); + + if (ds_log.head == NULL) { + + new->prev = new; + new->next = new; + + ds_log.head = new; + } else { + ds_log_entry_t *head = ds_log.head; + ds_log_entry_t *tail = ds_log.head->prev; + + new->next = head; + new->prev = tail; + tail->next = new; + head->prev = new; + } + + /* increase the log size, including the metadata size */ + ds_log.size += DS_LOG_ENTRY_SZ(new); + ds_log.nentry++; + + DS_DBG_LOG(CE_NOTE, "ds_log: added %ld data bytes, %ld total bytes", + new->datasz, DS_LOG_ENTRY_SZ(new)); + + return (0); +} + +/* + * Remove an entry from the head of the log + */ +static int +ds_log_remove(void) +{ + ds_log_entry_t *head; + + ASSERT(MUTEX_HELD(&ds_log.lock)); + + head = ds_log.head; + + /* empty list */ + if (head == NULL) + return (0); + + if (head->next == ds_log.head) { + /* one element list */ + ds_log.head = NULL; + } else { + head->next->prev = head->prev; + head->prev->next = head->next; + ds_log.head = head->next; + } + + DS_DBG_LOG(CE_NOTE, "ds_log: removed %ld data bytes, %ld total bytes", + head->datasz, DS_LOG_ENTRY_SZ(head)); + + ds_log.size -= DS_LOG_ENTRY_SZ(head); + ds_log.nentry--; + + ds_log_entry_free(head); + + return (0); +} + +/* + * Replace the data in the entry at the front of the list with then + * new data. This has the effect of removing the oldest entry and + * adding the new entry. + */ +static int +ds_log_replace(int32_t dest, uint8_t *msg, size_t sz) +{ + ds_log_entry_t *head; + + ASSERT(MUTEX_HELD(&ds_log.lock)); + + head = ds_log.head; + + DS_DBG_LOG(CE_NOTE, "ds_log: replaced %ld data bytes (%ld total) with " + " %ld data bytes (%ld total)", head->datasz, + DS_LOG_ENTRY_SZ(head), sz, sz + sizeof (ds_log_entry_t)); + + ds_log.size -= DS_LOG_ENTRY_SZ(head); + + kmem_free(head->data, head->datasz); + + head->data = msg; + head->datasz = sz; + head->timestamp = ddi_get_time(); + head->dest = dest; + + ds_log.size += DS_LOG_ENTRY_SZ(head); + + ds_log.head = head->next; + + return (0); +} + +static void +ds_log_purge(void *arg) +{ + _NOTE(ARGUNUSED(arg)) + + mutex_enter(&ds_log.lock); + + DS_DBG_LOG(CE_NOTE, "ds_log: purging oldest log entries"); + + while ((ds_log.nentry) && (ds_log.size >= ds_log_sz)) { + (void) ds_log_remove(); + } + + mutex_exit(&ds_log.lock); +} + +int +ds_log_add_msg(int32_t dest, uint8_t *msg, size_t sz) +{ + int rv = 0; + void *data; + + mutex_enter(&ds_log.lock); + + /* allocate a local copy of the data */ + data = kmem_alloc(sz, KM_SLEEP); + bcopy(msg, data, sz); + + /* check if the log is larger than the soft limit */ + if ((ds_log.nentry) && ((ds_log.size + sz) >= ds_log_sz)) { + /* + * The log is larger than the soft limit. + * Swap the oldest entry for the newest. + */ + DS_DBG_LOG(CE_NOTE, "%s: replacing oldest entry with new entry", + __func__); + (void) ds_log_replace(dest, data, sz); + } else { + /* + * Still have headroom under the soft limit. + * Add the new entry to the log. + */ + ds_log_entry_t *new; + + new = ds_log_entry_alloc(); + + /* fill in message data */ + new->data = data; + new->datasz = sz; + new->timestamp = ddi_get_time(); + new->dest = dest; + + rv = ds_log_add(new); + } + + /* check if the log is larger than the hard limit */ + if ((ds_log.nentry > 1) && (ds_log.size >= DS_LOG_LIMIT)) { + /* + * Wakeup the thread to remove entries + * from the log until it is smaller than + * the soft limit. + */ + DS_DBG_LOG(CE_NOTE, "%s: log exceeded %d bytes, scheduling" + " a purge...", __func__, DS_LOG_LIMIT); + + if (DS_DISPATCH(ds_log_purge, NULL) == NULL) { + cmn_err(CE_NOTE, "%s: purge thread failed to start", + __func__); + } + } + + mutex_exit(&ds_log.lock); + + return (rv); +} + +int +ds_add_port(uint64_t port_id, uint64_t ldc_id, ds_domain_hdl_t dhdl, + char *dom_name, int verbose) +{ + ds_port_t *newport; + + /* sanity check the port id */ + if (port_id > DS_MAX_PORT_ID) { + cmn_err(CE_WARN, "%s: port ID %ld out of range", + __func__, port_id); + return (EINVAL); + } + + DS_DBG_MD(CE_NOTE, "%s: adding port ds@%ld, LDC: 0x%lx, dhdl: 0x%lx", + __func__, port_id, ldc_id, dhdl); + + /* get the port structure from the array of ports */ + newport = &ds_ports[port_id]; + + /* check for a duplicate port in the MD */ + if (newport->state != DS_PORT_FREE) { + if (verbose) { + cmn_err(CE_WARN, "ds@%lx: %s: port already exists", + port_id, __func__); + } + if (newport->domain_hdl == DS_DHDL_INVALID) { + newport->domain_hdl = dhdl; + } + if (newport->domain_name == NULL && dom_name != NULL) { + newport->domain_name = ds_strdup(dom_name); + } + return (EBUSY); + } + + /* initialize the port */ + newport->id = port_id; + newport->ldc.id = ldc_id; + newport->domain_hdl = dhdl; + if (dom_name) { + newport->domain_name = ds_strdup(dom_name); + } else + newport->domain_name = NULL; + ds_port_common_init(newport); + + return (0); +} + +int +ds_remove_port(uint64_t port_id, int is_fini) +{ + ds_port_t *port; + + if (port_id >= DS_MAX_PORTS || !DS_PORT_IN_SET(ds_allports, port_id)) { + DS_DBG_MD(CE_NOTE, "%s: invalid port %lx", __func__, + port_id); + return (EINVAL); + } + + DS_DBG_MD(CE_NOTE, "%s: removing port ds@%lx", __func__, port_id); + + port = &ds_ports[port_id]; + + mutex_enter(&port->lock); + + if (port->state >= DS_PORT_LDC_INIT) { + /* shut down the LDC for this port */ + (void) ds_ldc_fini(port); + } + + mutex_exit(&port->lock); + + if (port->domain_name) { + DS_FREE(port->domain_name, strlen(port->domain_name) + 1); + port->domain_name = NULL; + } + port->domain_hdl = DS_DHDL_INVALID; + + /* clean up the port structure */ + ds_port_common_fini(port, is_fini); + return (0); +} + +/* + * Interface for ds_service_lookup in lds driver. + */ +int +ds_service_lookup(ds_svc_hdl_t hdl, char **servicep, uint_t *is_client) +{ + ds_svc_t *svc; + + mutex_enter(&ds_svcs.lock); + if ((svc = ds_get_svc(hdl)) == NULL) { + mutex_exit(&ds_svcs.lock); + DS_DBG(CE_NOTE, "%s: handle 0x%llx not found", __func__, + (u_longlong_t)hdl); + return (ENXIO); + } + *servicep = svc->cap.svc_id; + *is_client = svc->flags & DSSF_ISCLIENT; + mutex_exit(&ds_svcs.lock); + return (0); +} + +/* + * Interface for ds_domain_lookup in lds driver. + */ +int +ds_domain_lookup(ds_svc_hdl_t hdl, ds_domain_hdl_t *dhdlp) +{ + ds_svc_t *svc; + + mutex_enter(&ds_svcs.lock); + if ((svc = ds_get_svc(hdl)) == NULL) { + mutex_exit(&ds_svcs.lock); + DS_DBG(CE_NOTE, "%s: handle 0x%llx not found", __func__, + (u_longlong_t)hdl); + return (ENXIO); + } + if (svc->port == NULL) + *dhdlp = ds_my_domain_hdl; + else + *dhdlp = svc->port->domain_hdl; + mutex_exit(&ds_svcs.lock); + return (0); +} + +/* + * Interface for ds_hdl_isready in lds driver. + */ +int +ds_hdl_isready(ds_svc_hdl_t hdl, uint_t *is_ready) +{ + ds_svc_t *svc; + + mutex_enter(&ds_svcs.lock); + if ((svc = ds_get_svc(hdl)) == NULL) { + mutex_exit(&ds_svcs.lock); + DS_DBG(CE_NOTE, "%s: handle 0x%llx not found", __func__, + (u_longlong_t)hdl); + return (ENXIO); + } + *is_ready = (svc->state == DS_SVC_ACTIVE); + mutex_exit(&ds_svcs.lock); + return (0); +} + +/* + * Interface for ds_dom_name_to_hdl in lds driver. + */ +int +ds_dom_name_to_hdl(char *domain_name, ds_domain_hdl_t *dhdlp) +{ + int i; + ds_port_t *port; + + for (i = 0, port = ds_ports; i < DS_MAX_PORTS; i++, port++) { + if (port->state != DS_PORT_FREE && + port->domain_name != NULL && + strcmp(port->domain_name, domain_name) == 0) { + *dhdlp = port->domain_hdl; + return (0); + } + } + return (ENXIO); +} + +/* + * Interface for ds_dom_hdl_to_name in lds driver. + */ +int +ds_dom_hdl_to_name(ds_domain_hdl_t dhdl, char **domain_namep) +{ + int i; + ds_port_t *port; + + for (i = 0, port = ds_ports; i < DS_MAX_PORTS; i++, port++) { + if (port->state != DS_PORT_FREE && + port->domain_hdl == dhdl) { + *domain_namep = port->domain_name; + return (0); + } + } + return (ENXIO); +} + +/* + * Unregister all handles related to device open instance. + */ +void +ds_unreg_all(int instance) +{ + int idx; + ds_svc_t *svc; + ds_svc_hdl_t hdl; + + DS_DBG_USR(CE_NOTE, "%s: entered", __func__); + + /* walk every table entry */ + mutex_enter(&ds_svcs.lock); + for (idx = 0; idx < ds_svcs.maxsvcs; idx++) { + svc = ds_svcs.tbl[idx]; + if (DS_SVC_ISFREE(svc)) + continue; + if ((svc->flags & DSSF_ISUSER) != 0 && svc->drvi == instance) { + hdl = svc->hdl; + mutex_exit(&ds_svcs.lock); + (void) ds_unreg_hdl(hdl); + mutex_enter(&ds_svcs.lock); + DS_DBG_USR(CE_NOTE, "%s: ds_unreg_hdl(0x%llx):", + __func__, (u_longlong_t)hdl); + } + } + mutex_exit(&ds_svcs.lock); +} + +/* + * Special callbacks to allow the lds module revision-independent access + * to service structure data in the callback routines. This assumes that + * we put a special "cookie" in the arg argument passed to those + * routines (for now, a ptr to the svc structure, but it could be a svc + * table index or something that we could get back to the svc table entry). + */ +void +ds_cbarg_get_hdl(ds_cb_arg_t arg, ds_svc_hdl_t *hdlp) +{ + ds_svc_t *svc = (ds_svc_t *)arg; + + ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg); + *hdlp = svc->hdl; +} + +void +ds_cbarg_get_flags(ds_cb_arg_t arg, uint32_t *flagsp) +{ + ds_svc_t *svc = (ds_svc_t *)arg; + + ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg); + *flagsp = svc->flags; +} + +void +ds_cbarg_get_drv_info(ds_cb_arg_t arg, int *drvip) +{ + ds_svc_t *svc = (ds_svc_t *)arg; + + ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg); + *drvip = svc->drvi; +} + +void +ds_cbarg_get_drv_per_svc_ptr(ds_cb_arg_t arg, void **dpspp) +{ + ds_svc_t *svc = (ds_svc_t *)arg; + + ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg); + *dpspp = svc->drv_psp; +} + +void +ds_cbarg_get_domain(ds_cb_arg_t arg, ds_domain_hdl_t *dhdlp) +{ + ds_svc_t *svc = (ds_svc_t *)arg; + + ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg); + if (svc->port == NULL) + *dhdlp = ds_my_domain_hdl; + else + *dhdlp = svc->port->domain_hdl; +} + +void +ds_cbarg_get_service_id(ds_cb_arg_t arg, char **servicep) +{ + ds_svc_t *svc = (ds_svc_t *)arg; + + ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg); + *servicep = svc->cap.svc_id; +} + +void +ds_cbarg_set_drv_per_svc_ptr(ds_cb_arg_t arg, void *dpsp) +{ + ds_svc_t *svc = (ds_svc_t *)arg; + + ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg); + svc->drv_psp = dpsp; +} + +void +ds_cbarg_set_cookie(ds_svc_t *svc) +{ + svc->ops.cb_arg = (ds_cb_arg_t)(svc); +} + +int +ds_hdl_get_cbarg(ds_svc_hdl_t hdl, ds_cb_arg_t *cbargp) +{ + ds_svc_t *svc; + + mutex_enter(&ds_svcs.lock); + if ((svc = ds_get_svc(hdl)) != NULL && + (svc->flags & DSSF_ISUSER) != 0) { + ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg); + *cbargp = svc->ops.cb_arg; + mutex_exit(&ds_svcs.lock); + return (0); + } + mutex_exit(&ds_svcs.lock); + return (ENXIO); +} + +int +ds_is_my_hdl(ds_svc_hdl_t hdl, int instance) +{ + ds_svc_t *svc; + int rv = 0; + + mutex_enter(&ds_svcs.lock); + if ((svc = ds_get_svc(hdl)) == NULL) { + DS_DBG_USR(CE_NOTE, "%s: invalid hdl: 0x%llx\n", __func__, + (u_longlong_t)hdl); + rv = ENXIO; + } else if (instance == DS_INVALID_INSTANCE) { + if ((svc->flags & DSSF_ISUSER) != 0) { + DS_DBG_USR(CE_NOTE, "%s: unowned hdl: 0x%llx\n", + __func__, (u_longlong_t)hdl); + rv = EACCES; + } + } else if ((svc->flags & DSSF_ISUSER) == 0 || svc->drvi != instance) { + DS_DBG_USR(CE_NOTE, "%s: unowned hdl: 0x%llx\n", __func__, + (u_longlong_t)hdl); + rv = EACCES; + } + mutex_exit(&ds_svcs.lock); + return (rv); +} diff --git a/usr/src/uts/sun4v/io/vlds.c b/usr/src/uts/sun4v/io/vlds.c new file mode 100644 index 0000000000..25b1e3edb4 --- /dev/null +++ b/usr/src/uts/sun4v/io/vlds.c @@ -0,0 +1,1576 @@ +/* + * 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. + */ + +/* + * LDOMs Domain Services Device Driver + */ +#include <sys/types.h> +#include <sys/file.h> +#include <sys/errno.h> +#include <sys/open.h> +#include <sys/cred.h> +#include <sys/uio.h> +#include <sys/stat.h> +#include <sys/ksynch.h> +#include <sys/modctl.h> +#include <sys/conf.h> +#include <sys/devops.h> +#include <sys/debug.h> +#include <sys/cmn_err.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/taskq.h> +#include <sys/disp.h> +#include <sys/note.h> +#include <sys/mach_descrip.h> +#include <sys/mdesc.h> +#include <sys/mdeg.h> +#include <sys/ldc.h> +#include <sys/ds.h> +#include <sys/ds_impl.h> +#include <sys/vlds.h> +#include <sys/bitmap.h> +#include <sys/sysevent.h> + +static dev_info_t *vlds_devi; + + +typedef struct vlds_state { + dev_info_t *dip; + int instance; + evchan_t *evchan; +} vlds_state_t; + +static void *vlds_statep; + +typedef struct vlds_recv_hdr { + struct vlds_recv_hdr *next; /* next in recv list */ + void *data; /* the data itself */ + size_t datasz; /* size of the data */ +} vlds_recv_hdr_t; + +typedef struct vlds_svc_info { + int state; /* driver svc info state VLDS_RECV* */ + vlds_recv_hdr_t *recv_headp; /* ptr to head of recv queue */ + vlds_recv_hdr_t *recv_tailp; /* ptr to tail of recv queue */ + size_t recv_size; /* no. of bytes in recv queue */ + kmutex_t recv_lock; /* lock for recv queue */ + kcondvar_t recv_cv; /* condition variable for recv queue */ + int recv_nreaders; /* no of currently waiting readers */ +} vlds_svc_info_t; + +#define VLDS_RECV_OK 1 +#define VLDS_RECV_UNREG_PENDING 2 + +static int vlds_ports_inited = 0; + +static uint_t vlds_flags_to_svc(uint64_t flags); + + +#define VLDS_NAME "vlds" +static int vlds_open(dev_t *devp, int flag, int otyp, cred_t *credp); +static int vlds_close(dev_t dev, int flag, int otyp, cred_t *credp); +static int vlds_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, + int *rvalp); +static int vlds_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, + void **resultp); +static int vlds_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); +static int vlds_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); + +/* mdeg register functions */ +static int ds_mdeg_cb(void *cb_argp, mdeg_result_t *resp); +static int ds_mdeg_register(void); +static int ds_mdeg_unregister(void); +static int ds_add_mdeg_port(md_t *mdp, mde_cookie_t node); + +/* driver utilities */ +static void vlds_user_reg_cb(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl); +static void vlds_user_unreg_cb(ds_cb_arg_t arg); +static void vlds_user_data_cb(ds_cb_arg_t arg, void *buf, size_t buflen); +static void vlds_recvq_init(vlds_svc_info_t *dpsp); +static void vlds_recvq_destroy(vlds_svc_info_t *dpsp); +static int vlds_recvq_get_data(vlds_svc_info_t *dpsp, void *buf, size_t buflen, + size_t *msglenp, int mode); +static void vlds_recvq_drain(vlds_svc_info_t *dpsp); +static int vlds_recvq_put_data(vlds_svc_info_t *dpsp, void *buf, size_t buflen); +static int vlds_recv_msg(ds_svc_hdl_t hdl, void *buf, size_t buflen, + size_t *msglenp, int mode); + +/* + * DS driver Ops Vector + */ +static struct cb_ops vlds_cb_ops = { + vlds_open, /* cb_open */ + vlds_close, /* cb_close */ + nodev, /* cb_strategy */ + nodev, /* cb_print */ + nodev, /* cb_dump */ + nodev, /* cb_read */ + nodev, /* cb_write */ + vlds_ioctl, /* cb_ioctl */ + nodev, /* cb_devmap */ + nodev, /* cb_mmap */ + nodev, /* cb_segmap */ + nochpoll, /* cb_chpoll */ + ddi_prop_op, /* cb_prop_op */ + (struct streamtab *)NULL, /* cb_str */ + D_MP | D_64BIT, /* cb_flag */ + CB_REV, /* cb_rev */ + nodev, /* cb_aread */ + nodev /* cb_awrite */ +}; + +static struct dev_ops vlds_dev_ops = { + DEVO_REV, /* devo_rev */ + 0, /* devo_refcnt */ + vlds_getinfo, /* devo_getinfo */ + nulldev, /* devo_identify */ + nulldev, /* devo_probe */ + vlds_attach, /* devo_attach */ + vlds_detach, /* devo_detach */ + nodev, /* devo_reset */ + &vlds_cb_ops, /* devo_cb_ops */ + (struct bus_ops *)NULL, /* devo_bus_ops */ + nulldev /* devo_power */ +}; + +static struct modldrv modldrv = { + &mod_driverops, + "Domain Services Driver 1.0", + &vlds_dev_ops +}; + +static struct modlinkage modlinkage = { + MODREV_1, + (void *)&modldrv, + NULL +}; + +/* + * Callback ops for user-land services. + */ +static ds_clnt_ops_t ds_user_ops = { + vlds_user_reg_cb, /* register */ + vlds_user_unreg_cb, /* unregister */ + vlds_user_data_cb, /* data */ + NULL /* ds_ucap_init will fill in */ +}; + +#define VLDS_MINOR_MAX SHRT_MAX + +/* Definitions for binding handle array */ +static ulong_t vlds_bitmap_initial = 1; /* index 0 indicates error */ +static ulong_t *vlds_minor_bitmap = &vlds_bitmap_initial; +static size_t vlds_minor_bits = BT_NBIPUL; +static kmutex_t vlds_minor_mutex; + +/* + * Following vlds_minor_* routines map a binding handle to a minor number. + * Has to be called w/ locks held. + */ +static ulong_t * +vlds_minor_alloc(void) +{ + ulong_t *bhst = vlds_minor_bitmap; + + /* Increase bitmap by one BT_NBIPUL */ + if (vlds_minor_bits + BT_NBIPUL > VLDS_MINOR_MAX) { + return ((ulong_t *)NULL); + } + vlds_minor_bitmap = kmem_zalloc( + BT_SIZEOFMAP(vlds_minor_bits + BT_NBIPUL), KM_SLEEP); + bcopy(bhst, vlds_minor_bitmap, BT_SIZEOFMAP(vlds_minor_bits)); + if (bhst != &vlds_bitmap_initial) + kmem_free(bhst, BT_SIZEOFMAP(vlds_minor_bits)); + vlds_minor_bits += BT_NBIPUL; + + return (vlds_minor_bitmap); +} + +static void +vlds_minor_free(ulong_t *bitmap) +{ + if (bitmap != &vlds_bitmap_initial) + kmem_free(bitmap, BT_SIZEOFMAP(vlds_minor_bits)); +} + +static index_t +vlds_minor_get(void) +{ + index_t idx; + ulong_t *bhst; + + /* Search for an available index */ + mutex_enter(&vlds_minor_mutex); + if ((idx = bt_availbit(vlds_minor_bitmap, + vlds_minor_bits)) == -1) { + /* All busy - allocate additional binding handle bitmap space */ + if ((bhst = vlds_minor_alloc()) == NULL) { + /* Reached our maximum of id's == SHRT_MAX */ + mutex_exit(&vlds_minor_mutex); + return (0); + } else { + vlds_minor_bitmap = bhst; + } + idx = bt_availbit(vlds_minor_bitmap, vlds_minor_bits); + } + BT_SET(vlds_minor_bitmap, idx); + mutex_exit(&vlds_minor_mutex); + return (idx); +} + +static void +vlds_minor_rele(index_t idx) +{ + mutex_enter(&vlds_minor_mutex); + ASSERT(BT_TEST(vlds_minor_bitmap, idx) == 1); + BT_CLEAR(vlds_minor_bitmap, idx); + mutex_exit(&vlds_minor_mutex); +} + +static void +vlds_minor_init(void) +{ + mutex_init(&vlds_minor_mutex, NULL, MUTEX_DEFAULT, NULL); +} + +int +_init(void) +{ + int s; + + if ((s = ddi_soft_state_init(&vlds_statep, sizeof (vlds_state_t), 0)) + != 0) + return (s); + + if ((s = mod_install(&modlinkage)) != 0) { + ddi_soft_state_fini(&vlds_statep); + return (s); + } + + (void) ds_mdeg_register(); + + return (s); +} + +int +_fini(void) +{ + int s; + + if ((s = mod_remove(&modlinkage)) != 0) + return (s); + + ddi_soft_state_fini(&vlds_statep); + + (void) ds_mdeg_unregister(); + + return (s); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + + + +/*ARGSUSED*/ +static int +vlds_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) +{ + switch (cmd) { + case DDI_INFO_DEVT2DEVINFO: + *resultp = vlds_devi; + return (DDI_SUCCESS); + case DDI_INFO_DEVT2INSTANCE: + *resultp = 0; + return (DDI_SUCCESS); + } + return (DDI_FAILURE); +} + + +static int +vlds_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) +{ + if (cmd != DDI_ATTACH) { + return (DDI_FAILURE); + } + + if (ddi_create_minor_node(devi, VLDS_NAME, S_IFCHR, + 0, DDI_PSEUDO, NULL) == DDI_FAILURE) { + ddi_remove_minor_node(devi, NULL); + return (DDI_FAILURE); + } + vlds_devi = devi; + + vlds_minor_init(); + + return (DDI_SUCCESS); +} + + +/*ARGSUSED*/ +static int +vlds_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) +{ + if (cmd != DDI_DETACH) { + return (DDI_FAILURE); + } + + vlds_minor_free(vlds_minor_bitmap); + ddi_remove_minor_node(devi, NULL); + return (DDI_SUCCESS); +} + + +/*ARGSUSED*/ +static int +vlds_open(dev_t *devp, int flag, int otyp, cred_t *credp) +{ + int minor; + + if (otyp != OTYP_CHR) + return (EINVAL); + + if (getminor(*devp) != 0) + return (ENXIO); + + minor = vlds_minor_get(); + if (minor == 0) + /* All minors are busy */ + return (EBUSY); + + if (ddi_soft_state_zalloc(vlds_statep, minor) != DDI_SUCCESS) { + vlds_minor_rele(minor); + return (ENOMEM); + } + + *devp = makedevice(getmajor(*devp), minor); + + return (0); +} + + +/*ARGSUSED*/ +static int +vlds_close(dev_t dev, int flag, int otyp, cred_t *credp) +{ + int minor = (int)getminor(dev); + vlds_state_t *sp; + + DS_DBG_VLDS(CE_NOTE, "vlds_close"); + + /* + * Unregister all handles associated with this process. + */ + ds_unreg_all(minor); + + if (otyp != OTYP_CHR) + return (EINVAL); + + sp = ddi_get_soft_state(vlds_statep, minor); + if (sp == NULL) { + return (ENXIO); + } + + if (sp->evchan) { + sysevent_evc_unbind(sp->evchan); + sp->evchan = NULL; + } + + ddi_soft_state_free(vlds_statep, minor); + vlds_minor_rele(minor); + + return (0); +} + +int +vlds_init_sysevent(vlds_state_t *sp, uint32_t flags) +{ + char evchan_name[MAX_CHNAME_LEN]; + int rv; + + if (flags & DSSF_ANYCB_VALID) { + if (sp->evchan) { + DS_DBG_VLDS(CE_NOTE, "%s: sysevent already bound", + __func__); + return (0); + } + (void) sprintf(evchan_name, VLDS_SYSEV_CHAN_FMT, ddi_get_pid()); + if ((rv = sysevent_evc_bind(evchan_name, &sp->evchan, + EVCH_CREAT|EVCH_HOLD_PEND)) != 0) { + cmn_err(CE_WARN, "%s: can't bind to '%s' (%d)", + __func__, evchan_name, rv); + return (rv); + } + + DS_DBG_VLDS(CE_NOTE, "%s: sysevent bind to '%s' successful", + __func__, evchan_name); + } + return (0); +} + +#define ARGTOPTR(x) ((void *)((uintptr_t)(x))) +#define ARGTOUINT(x) ((uint_t)(x)) +#define ARGTOINT(x) ((int)(x)) + +static int +vlds_get_string(vlds_string_t *strp, char **rstrp, int mode) +{ + char *str; + uint_t len = strp->vlds_strlen; + uint_t slen; + + if (len == 0) { + *rstrp = NULL; + return (0); + } + if (len > MAXNAMELEN) { + DS_DBG_VLDS(CE_NOTE, "%s: invalid string length: %d", __func__, + len); + return (EINVAL); + } + str = DS_MALLOC(len); + if (ddi_copyin(ARGTOPTR(strp->vlds_strp), str, len, mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: ddi copyin failed (%p)", __func__, + ARGTOPTR(strp->vlds_strp)); + DS_FREE(str, len); + return (EFAULT); + } + slen = strlen(str) + 1; + if (slen != len) { + DS_DBG_VLDS(CE_NOTE, "%s: invalid string len: %d != len: %d", + __func__, slen, len); + DS_FREE(str, len); + return (EINVAL); + } + *rstrp = str; + return (0); +} + +static int +vlds_put_string(char *str, vlds_string_t *strp, int mode) +{ + uint_t len; + char *tstr = NULL; + int rv; + + if (str == NULL) { + str = ""; + } + len = strlen(str) + 1; + + /* + * If string is longer than user buffer, return a + * truncated, null-terminated string. + */ + if (len > strp->vlds_strlen) { + len = strp->vlds_strlen; + if (len > 0) { + tstr = DS_MALLOC(len); + (void) memcpy(tstr, str, len - 1); + tstr[len - 1] = '\0'; + str = tstr; + } + } + rv = ddi_copyout(str, ARGTOPTR(strp->vlds_strp), len, mode); + if (tstr) { + DS_FREE(tstr, len); + } + if (rv) { + DS_DBG_VLDS(CE_NOTE, "%s: copyout (%p) failed", __func__, + ARGTOPTR(strp->vlds_strp)); + return (EFAULT); + } + return (0); +} + +static int +vlds_get_ucap(vlds_cap_t *capp, ds_capability_t *ucap, int mode) +{ + char *servp; + vlds_ver_t *dsvp; + vlds_cap_t vlds_cap; + uint_t n; + uint_t nver; + int i; + int rv; + + if (ddi_copyin(capp, &vlds_cap, sizeof (vlds_cap), mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: cap copyin failed (%p)", __func__, + (void *)capp); + return (EFAULT); + } + + nver = ARGTOUINT(vlds_cap.vlds_nver); + + if (nver > VLDS_MAX_VERS) { + DS_DBG_VLDS(CE_NOTE, "%s: vlds_nver (%d) invalid", __func__, + nver); + return (EINVAL); + } + + if ((rv = vlds_get_string(&vlds_cap.vlds_service, &servp, mode)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: vlds_get_string vlds_service failed " + "(%d)", __func__, rv); + return (rv); + } else if (servp == NULL) { + DS_DBG_VLDS(CE_NOTE, "%s: vlds_get_string vlds_service is NULL", + __func__); + return (EINVAL); + } + + n = nver * sizeof (vlds_ver_t); + dsvp = DS_MALLOC(n); + + if (ddi_copyin(ARGTOPTR(vlds_cap.vlds_versp), dsvp, n, mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: copyin of vers (%p, %d) failed", + __func__, ARGTOPTR(vlds_cap.vlds_versp), n); + DS_FREE(servp, strlen(servp) + 1); + DS_FREE(dsvp, n); + return (EFAULT); + } + + ucap->svc_id = servp; + ucap->vers = DS_MALLOC(nver * sizeof (ds_ver_t)); + for (i = 0; i < nver; i++) { + ucap->vers[i].major = dsvp[i].vlds_major; + ucap->vers[i].minor = dsvp[i].vlds_minor; + } + ucap->nvers = nver; + DS_FREE(dsvp, n); + return (0); +} + +static void +vlds_free_ucap(ds_capability_t *ucap) +{ + kmem_free(ucap->svc_id, strlen(ucap->svc_id) + 1); + kmem_free(ucap->vers, ucap->nvers * sizeof (ds_ver_t)); +} + +/*ARGSUSED*/ +static int +vlds_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, + int *rvalp) +{ + vlds_state_t *sp; + ds_svc_hdl_t hdl; + ds_domain_hdl_t dhdl; + char *servicep; + int rv; + int minor = (int)getminor(dev); + + if ((sp = ddi_get_soft_state(vlds_statep, minor)) == NULL) + return (ENXIO); + + switch (cmd) { + + case VLDS_SVC_REG: + { + vlds_svc_reg_arg_t vlds_arg; + ds_capability_t ucap; + uint64_t hdl_arg; + uint_t flags; + + if (ddi_copyin((void *)arg, &vlds_arg, sizeof (vlds_arg), + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: SVC REG arg copyin failed", + __func__); + return (EFAULT); + } + + if ((rv = vlds_get_ucap(ARGTOPTR(vlds_arg.vlds_capp), &ucap, + mode)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: SVC REG get_ucap failed (%d)", + __func__, rv); + return (rv); + } + + flags = vlds_flags_to_svc(vlds_arg.vlds_reg_flags); + if ((rv = vlds_init_sysevent(sp, flags)) != 0) { + vlds_free_ucap(&ucap); + return (rv); + } + + rv = ds_ucap_init(&ucap, &ds_user_ops, + vlds_flags_to_svc(vlds_arg.vlds_reg_flags) | DSSF_ISUSER, + minor, &hdl); + + vlds_free_ucap(&ucap); + + if (rv) { + DS_DBG_VLDS(CE_NOTE, "%s: SVC REG ds_ucap_init failed " + "(%d)", __func__, rv); + return (rv); + } + + hdl_arg = hdl; + if (ddi_copyout(&hdl_arg, ARGTOPTR(vlds_arg.vlds_hdlp), + sizeof (hdl_arg), mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: SVC REG copyout failed", + __func__); + return (EFAULT); + } + DS_DBG_VLDS(CE_NOTE, "%s: SVC REG succeeded: hdl: %lx", + __func__, hdl); + break; + } + + case VLDS_UNREG_HDL: + { + vlds_unreg_hdl_arg_t vlds_arg; + + if (ddi_copyin((void *)arg, &vlds_arg, sizeof (vlds_arg), + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: UNREG_HDL arg copyin failed", + __func__); + return (EFAULT); + } + + hdl = vlds_arg.vlds_hdl; + + if ((rv = ds_is_my_hdl(hdl, minor)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: UNREG_HDL ds_is_my_hdl " + " hdl: %lx inst: %d failed (%d)", __func__, + hdl, rv, minor); + return (rv); + } + + if ((rv = ds_unreg_hdl(hdl)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: UNREG_HDL ds_cap_unreg " + " hdl: %lx failed (%d)", __func__, hdl, rv); + return (rv); + } + DS_DBG_VLDS(CE_NOTE, "%s: UNREG_HDL hdl: %lx succeeded", + __func__, hdl); + break; + } + + case VLDS_HDL_LOOKUP: + { + vlds_hdl_lookup_arg_t vlds_arg; + ds_svc_hdl_t *hdlsp; + uint_t is_client, maxhdls, nhdls; + uint64_t nhdls_arg; + + if (ddi_copyin((void *)arg, &vlds_arg, sizeof (vlds_arg), + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: HDL_LOOKUP arg copyin failed", + __func__); + return (EFAULT); + } + + is_client = ARGTOUINT(vlds_arg.vlds_isclient); + maxhdls = ARGTOUINT(vlds_arg.vlds_maxhdls); + if (maxhdls == 0) { + DS_DBG_VLDS(CE_NOTE, "%s: HDL_LOOKUP invalid maxhdls " + "%d", __func__, maxhdls); + return (EINVAL); + } + + if ((rv = vlds_get_string(&vlds_arg.vlds_service, &servicep, + mode)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: HDL_LOOKUP vlds_get_string " + "(service) failed (%d)", __func__, rv); + return (EFAULT); + } else if (servicep == NULL) { + DS_DBG_VLDS(CE_NOTE, "%s: HDL_LOOKUP vlds_get_string " + " service is NULL", __func__); + return (EINVAL); + } + + if (ARGTOPTR(vlds_arg.vlds_hdlsp) == 0) { + hdlsp = NULL; + } else { + hdlsp = DS_MALLOC(maxhdls * sizeof (*hdlsp)); + } + + DS_DBG_VLDS(CE_NOTE, "%s: HDL_LOOKUP (%s, %d) entered", + __func__, servicep, is_client); + rv = ds_hdl_lookup(servicep, is_client, hdlsp, maxhdls, &nhdls); + + DS_FREE(servicep, strlen(servicep) + 1); + if (rv) { + if (hdlsp) { + DS_FREE(hdlsp, maxhdls * sizeof (*hdlsp)); + } + DS_DBG_VLDS(CE_NOTE, "%s: HDL_LOOKUP failed: (%d)", + __func__, rv); + return (rv); + } + + if (hdlsp != NULL && nhdls > 0 && + ddi_copyout(hdlsp, ARGTOPTR(vlds_arg.vlds_hdlsp), + nhdls * sizeof (ds_svc_hdl_t), mode) != 0) { + if (hdlsp) { + DS_FREE(hdlsp, maxhdls * sizeof (*hdlsp)); + } + DS_DBG_VLDS(CE_NOTE, "%s: HDL_LOOKUP copyout of hdls " + " failed", __func__); + return (EFAULT); + } + if (hdlsp) { + DS_FREE(hdlsp, maxhdls * sizeof (*hdlsp)); + } + + nhdls_arg = nhdls; + if (ddi_copyout(&nhdls_arg, ARGTOPTR(vlds_arg.vlds_nhdlsp), + sizeof (nhdls_arg), mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: HDL_LOOKUP copyout of nhdls " + " failed", __func__); + return (EFAULT); + } + DS_DBG_VLDS(CE_NOTE, "%s: HDL_LOOKUP succeeded: nhdls: %d", + __func__, nhdls); + break; + } + + case VLDS_DMN_LOOKUP: + { + vlds_dmn_lookup_arg_t vlds_arg; + uint64_t dhdl_arg; + + if (ddi_copyin((void *)arg, &vlds_arg, sizeof (vlds_arg), + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: DMN_LOOKUP arg copyin failed", + __func__); + return (EFAULT); + } + + hdl = vlds_arg.vlds_hdl; + + if ((rv = ds_domain_lookup(hdl, &dhdl)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: DMN_LOOKUP lookup hdl: 0x%lx " + "failed (%d)", __func__, hdl, rv); + return (rv); + } + + dhdl_arg = dhdl; + + if (ddi_copyout(&dhdl_arg, ARGTOPTR(vlds_arg.vlds_dhdlp), + sizeof (dhdl_arg), mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: DMN_LOOKUP copyout " + "failed (%d)", __func__, rv); + return (rv); + } + + DS_DBG_VLDS(CE_NOTE, "%s: DMN_LOOKUP hdl: 0x%lx, dhdl: 0x%lx " + "succeeded", __func__, hdl, dhdl); + break; + } + + case VLDS_SEND_MSG: + { + vlds_send_msg_arg_t vlds_arg; + size_t buflen; + char *bufp; + + if (ddi_copyin((void *)arg, &vlds_arg, sizeof (vlds_arg), + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: SEND_MSG arg copyin failed", + __func__); + return (EFAULT); + } + + hdl = vlds_arg.vlds_hdl; + if ((rv = ds_is_my_hdl(hdl, minor)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: SEND_MSG ds_is_my_hdl " + " hdl: %lx inst: %d failed (%d)", __func__, + hdl, rv, minor); + return (rv); + } + + buflen = ARGTOUINT(vlds_arg.vlds_buflen); + bufp = DS_MALLOC(buflen); + DS_DBG_VLDS(CE_NOTE, "%s: SEND_MSG (hdl: %lx, bufp: %p, " + "buflen: %ld", __func__, hdl, ARGTOPTR(vlds_arg.vlds_bufp), + buflen); + + if (ddi_copyin(ARGTOPTR(vlds_arg.vlds_bufp), bufp, buflen, + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: SEND_MSG buf (%p, %ld) " + "copyin failed", __func__, + ARGTOPTR(vlds_arg.vlds_bufp), buflen); + DS_FREE(bufp, buflen); + return (EFAULT); + } + + if ((rv = ds_cap_send(hdl, bufp, buflen)) != 0) { + DS_FREE(bufp, buflen); + DS_DBG_VLDS(CE_NOTE, "%s: SEND_MSG ds_cap_send failed " + "(%d)", __func__, rv); + return (rv); + } + DS_DBG_VLDS(CE_NOTE, "%s: SEND_MSG hdl: %lx, bufp: %p, " + "buflen: %ld succeeded", __func__, hdl, (void *)bufp, + buflen); + DS_DUMP_MSG(DS_DBG_FLAG_VLDS, bufp, buflen); + DS_FREE(bufp, buflen); + break; + } + + case VLDS_RECV_MSG: + { + vlds_recv_msg_arg_t vlds_arg; + size_t buflen, msglen; + uint64_t msglen_arg; + + if (ddi_copyin((void *)arg, &vlds_arg, sizeof (vlds_arg), + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: RECV_MSG arg copyin failed", + __func__); + return (EFAULT); + } + + hdl = vlds_arg.vlds_hdl; + if ((rv = ds_is_my_hdl(hdl, minor)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: RECV_MSG ds_is_my_hdl " + " hdl: %lx inst: %d failed (%d)", __func__, + hdl, rv, minor); + return (rv); + } + + buflen = ARGTOUINT(vlds_arg.vlds_buflen); + + if ((rv = vlds_recv_msg(hdl, ARGTOPTR(vlds_arg.vlds_bufp), + buflen, &msglen, mode)) != 0 && rv != EFBIG) { + DS_DBG_VLDS(CE_NOTE, "%s: RECV_MSG vlds_recv_msg " + " failed (%d)", __func__, rv); + return (rv); + } + + msglen_arg = msglen; + if (ddi_copyout(&msglen_arg, ARGTOPTR(vlds_arg.vlds_msglenp), + sizeof (msglen_arg), mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: RECV_MSG copyout of msglen " + "failed", __func__); + return (EFAULT); + } + + if (rv == EFBIG) { + return (EFBIG); + } + + DS_DBG_VLDS(CE_NOTE, "%s: RECV_MSG hdl: %lx, " + "msglen: %ld succeeded", __func__, hdl, buflen); + break; + } + + case VLDS_HDL_ISREADY: + { + vlds_hdl_isready_arg_t vlds_arg; + ds_svc_hdl_t hdl; + uint64_t is_ready_arg; + uint_t is_ready; + + if (ddi_copyin((void *)arg, &vlds_arg, sizeof (vlds_arg), + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: HDL_ISREADY arg copyin " + "failed", __func__); + return (EFAULT); + } + + hdl = vlds_arg.vlds_hdl; + if ((rv = ds_hdl_isready(hdl, &is_ready)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: HDL_ISREADY ds_hdl_isready " + "error (%d)", __func__, rv); + return (rv); + } + + is_ready_arg = is_ready; + if (ddi_copyout(&is_ready_arg, ARGTOPTR(vlds_arg.vlds_isreadyp), + sizeof (is_ready_arg), mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: HDL_ISREADY copyout of " + "vlds_isready failed", __func__); + return (EFAULT); + } + DS_DBG_VLDS(CE_NOTE, "%s: HDL_ISREADY succeeded hdl: %lx, " + "is_ready: %d", __func__, hdl, is_ready); + break; + } + + case VLDS_DOM_NAM2HDL: + { + vlds_dom_nam2hdl_arg_t vlds_arg; + char *domain_name; + uint64_t dhdl_arg; + ds_domain_hdl_t dhdl; + + if (ddi_copyin((void *)arg, &vlds_arg, sizeof (vlds_arg), + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: DOM_NAM2HDL arg copyin " + "failed", __func__); + return (EFAULT); + } + + if ((rv = vlds_get_string(&vlds_arg.vlds_domain_name, + &domain_name, mode)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: DOM_NAM2HDL vlds_get_string " + "domain_name failed (%d)", __func__, rv); + return (EFAULT); + } else if (servicep == NULL) { + DS_DBG_VLDS(CE_NOTE, "%s: DOM_NAM2HDL vlds_get_string " + " domain_name is NULL", __func__); + return (EINVAL); + } + + DS_DBG_VLDS(CE_NOTE, "%s: DOM_NAM2HDL (%s) entered", __func__, + domain_name); + + if ((rv = ds_dom_name_to_hdl(domain_name, &dhdl)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: DOM_NAM2HDL name: '%s' " + "failed: (%d)", __func__, domain_name, rv); + DS_FREE(domain_name, strlen(domain_name) + 1); + return (rv); + } + + dhdl_arg = dhdl; + if (ddi_copyout(&dhdl_arg, ARGTOPTR(vlds_arg.vlds_dhdlp), + sizeof (dhdl_arg), mode) != 0) { + DS_FREE(domain_name, strlen(domain_name) + 1); + DS_DBG_VLDS(CE_NOTE, "%s: DOM_NAM2HDL copyout of dhdl " + " failed", __func__); + return (EFAULT); + } + + DS_DBG_VLDS(CE_NOTE, "%s: DOM_NAM2HDL succeeded: name: '%s', " + "dhdl: 0x%lx", __func__, domain_name, dhdl); + DS_FREE(domain_name, strlen(domain_name) + 1); + break; + } + + case VLDS_DOM_HDL2NAM: + { + vlds_dom_hdl2nam_arg_t vlds_arg; + ds_domain_hdl_t dhdl; + char *domain_name; + + if (ddi_copyin((void *)arg, &vlds_arg, sizeof (vlds_arg), + mode) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: DOM_HDL2NAM arg copyin " + "failed", __func__); + return (EFAULT); + } + + dhdl = vlds_arg.vlds_dhdl; + if ((rv = ds_dom_hdl_to_name(hdl, &domain_name)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: DOM_HDL2NAM lookup dhdl: %lx " + "failed (%d)", __func__, dhdl, rv); + return (rv); + } + + if ((rv = vlds_put_string(domain_name, + &vlds_arg.vlds_domain_name, mode)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: DOM_HDL2NAM vlds_put_string " + "'%s' failed (%d)", __func__, domain_name, rv); + return (rv); + } + + DS_DBG_VLDS(CE_NOTE, "%s: DOM_HDL2NAM dhdl: 0x%lx name: '%s'", + __func__, dhdl, domain_name); + break; + } + + default: + return (EINVAL); + } + return (0); +} + +static uint_t +vlds_flags_to_svc(uint64_t flags) +{ + uint_t sflags = 0; + + if (flags & VLDS_REG_CLIENT) + sflags |= DSSF_ISCLIENT; + if (flags & VLDS_REGCB_VALID) + sflags |= DSSF_REGCB_VALID; + if (flags & VLDS_UNREGCB_VALID) + sflags |= DSSF_UNREGCB_VALID; + if (flags & VLDS_DATACB_VALID) + sflags |= DSSF_DATACB_VALID; + return (sflags); +} + +/* + * MD registration code. + * Placed in vlds rather than ds module due to cirular dependency of + * platsvc module which contains the mdeg code. + */ +mdeg_handle_t ds_mdeg_hdl; + +/* + * There's only one domain services node, so we don't + * need to specify any match conditions. However, we + * have to supply a non-NULL property spec. + */ +static mdeg_prop_spec_t ds_prop_template[] = { + { MDET_LIST_END, NULL, NULL } +}; + +static mdeg_node_spec_t ds_node_template = + { VLDS_MD_ROOT_NODE_NAME, ds_prop_template }; + +/* + * Matching criteria passed to the MDEG to register interest + * in changes to domain services port nodes identified by their + * 'id' property. + */ +static md_prop_match_t ds_port_prop_match[] = { + { MDET_PROP_VAL, "id" }, + { MDET_LIST_END, NULL } +}; + +static mdeg_node_match_t ds_port_match = { VLDS_MD_PORT_NODE_NAME, + ds_port_prop_match }; + +/* mdeg callback */ +static int +ds_mdeg_cb(void *cb_argp, mdeg_result_t *resp) +{ + _NOTE(ARGUNUSED(cb_argp)) + int idx; + uint64_t portno; + int rv; + md_t *mdp; + mde_cookie_t node; + + if (resp == NULL) { + DS_DBG_VLDS(CE_NOTE, "ds_mdeg_cb: no result returned"); + return (MDEG_FAILURE); + } + + DS_DBG_VLDS(CE_NOTE, "%s: added=%d, removed=%d, matched=%d", __func__, + 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]; + + DS_DBG_VLDS(CE_NOTE, "%s: processing added node 0x%lx", + __func__, node); + + /* attempt to add a port */ + if ((rv = ds_add_mdeg_port(mdp, node)) != MDEG_SUCCESS) { + if (vlds_ports_inited) { + cmn_err(CE_NOTE, "%s: unable to add port, " + "err = %d", __func__, rv); + } + } + } + + /* process removed ports */ + for (idx = 0; idx < resp->removed.nelem; idx++) { + mdp = resp->removed.mdp; + node = resp->removed.mdep[idx]; + + DS_DBG_VLDS(CE_NOTE, "%s: processing removed node 0x%lx", + __func__, node); + + /* read in the port's id property */ + if (md_get_prop_val(mdp, node, "id", &portno)) { + cmn_err(CE_NOTE, "%s: node 0x%lx of removed list " + "has no 'id' property", __func__, node); + continue; + } + + /* attempt to remove a port */ + if ((rv = ds_remove_port(portno, 0)) != 0) { + cmn_err(CE_NOTE, "%s: unable to remove port %lu, " + " err %d", __func__, portno, rv); + } + } + + vlds_ports_inited = 1; + + return (MDEG_SUCCESS); +} + +/* register callback to mdeg */ +static int +ds_mdeg_register(void) +{ + int rv; + + DS_DBG_VLDS(CE_NOTE, "ds_mdeg_register: entered"); + + /* perform the registration */ + rv = mdeg_register(&ds_node_template, &ds_port_match, ds_mdeg_cb, + NULL, &ds_mdeg_hdl); + + if (rv != MDEG_SUCCESS) { + cmn_err(CE_NOTE, "ds_mdeg_register: mdeg_register " + "failed, err = %d", rv); + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + +/* unregister callback from mdeg */ +static int +ds_mdeg_unregister(void) +{ + DS_DBG_VLDS(CE_NOTE, "ds_mdeg_unregister: hdl=0x%lx", ds_mdeg_hdl); + + return (mdeg_unregister(ds_mdeg_hdl)); +} + +static int +ds_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, "%s: invalid number of channel-endpoint nodes " + "found (%d)", __func__, 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, "%s: no channel-endpoint nodes found", + __func__); + kmem_free(listp, listsz); + return (-1); + } + + DS_DBG_VLDS(CE_NOTE, "%s: %d channel-endpoint nodes found", __func__, + nchan); + + /* use property from first node found */ + if (md_get_prop_val(mdp, listp[0], "id", ldc_id)) { + cmn_err(CE_NOTE, "%s: channel-endpoint has no 'id' property", + __func__); + kmem_free(listp, listsz); + return (-1); + } + + kmem_free(listp, listsz); + + return (0); +} + +/* add a DS services port */ +static int +ds_add_mdeg_port(md_t *mdp, mde_cookie_t node) +{ + uint64_t portno; + uint64_t ldc_id; + int rv; + uint64_t dhdl; + char *dom_name; + + /* read in the port's id property */ + if (md_get_prop_val(mdp, node, "id", &portno)) { + cmn_err(CE_NOTE, "%s: node 0x%lx of added list has no " + "'id' property", __func__, node); + return (MDEG_FAILURE); + } + + if (portno >= DS_MAX_PORTS) { + cmn_err(CE_NOTE, "%s: found port number (%lu) " + "larger than maximum supported number of ports", __func__, + portno); + return (MDEG_FAILURE); + } + + /* get all channels for this device (currently only one) */ + if (ds_get_port_channel(mdp, node, &ldc_id) == -1) { + return (MDEG_FAILURE); + } + + if (md_get_prop_val(mdp, node, "remote-domain-id", &dhdl) != 0) { + dhdl = DS_DHDL_INVALID; + } + + if (md_get_prop_str(mdp, node, "remote-domain-name", &dom_name) != 0) { + dom_name = NULL; + } + + rv = ds_add_port(portno, ldc_id, dhdl, dom_name, vlds_ports_inited); + + if (rv != 0) { + if (vlds_ports_inited) { + DS_DBG_VLDS(CE_NOTE, "ds%lx: %s LDC chan: %lx " + "failed err = %d", portno, __func__, ldc_id, rv); + } + return (MDEG_FAILURE); + } + + DS_DBG_VLDS(CE_NOTE, "ds%lx: %s LDC chan: %lx inited", portno, + __func__, ldc_id); + + return (MDEG_SUCCESS); +} + +static void +vlds_user_reg_cb(ds_cb_arg_t arg, ds_ver_t *ver, ds_svc_hdl_t hdl) +{ + nvlist_t *nvl = NULL; + ds_domain_hdl_t dhdl; + char *servicep; + uint32_t flags; + int minor; + vlds_state_t *sp; + vlds_svc_info_t *dpsp; + + ds_cbarg_get_flags(arg, &flags); + ASSERT((flags & DSSF_ISUSER) != 0); + + if ((flags & DSSF_DATACB_VALID) == 0) { + /* + * must allocate and init the svc read queue. + */ + DS_DBG_VLDS(CE_NOTE, "%s: hdl: 0x%lx initing recvq", __func__, + hdl); + dpsp = DS_MALLOC(sizeof (vlds_svc_info_t)); + vlds_recvq_init(dpsp); + ds_cbarg_set_drv_per_svc_ptr(arg, dpsp); + } + + if ((flags & DSSF_REGCB_VALID) != 0) { + ds_cbarg_get_drv_info(arg, &minor); + sp = ddi_get_soft_state(vlds_statep, minor); + ASSERT(sp != NULL); + ASSERT(sp->evchan != NULL); + ds_cbarg_get_domain(arg, &dhdl); + ds_cbarg_get_service_id(arg, &servicep); + DS_DBG_VLDS(CE_NOTE, "%s: regcb: hdl: 0x%lx, ver%d.%d, " + " dhdl: 0x%lx", __func__, hdl, ver->major, + ver->minor, dhdl); + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP) || + nvlist_add_uint64(nvl, VLDS_HDL, hdl) || + nvlist_add_uint16(nvl, VLDS_VER_MAJOR, ver->major) || + nvlist_add_uint16(nvl, VLDS_VER_MINOR, ver->minor) || + nvlist_add_uint64(nvl, VLDS_DOMAIN_HDL, dhdl) || + nvlist_add_string(nvl, VLDS_SERVICE_ID, servicep) || + nvlist_add_boolean_value(nvl, VLDS_ISCLIENT, + (flags & DSSF_ISCLIENT) != 0) || + sysevent_evc_publish(sp->evchan, EC_VLDS, + ESC_VLDS_REGISTER, "sun.com", "kernel", nvl, EVCH_SLEEP)) { + cmn_err(CE_WARN, "Failed to send REG Callback"); + } else { + DS_DBG_VLDS(CE_NOTE, "%s: sysevent_evc_publish " + "succeeded", __func__); + } + nvlist_free(nvl); + } +} + +static void +vlds_user_unreg_cb(ds_cb_arg_t arg) +{ + nvlist_t *nvl = NULL; + int minor; + ds_svc_hdl_t hdl; + vlds_state_t *sp; + void *dpsp; + uint32_t flags; + + ds_cbarg_get_flags(arg, &flags); + ASSERT((flags & DSSF_ISUSER) != 0); + + if ((flags & DSSF_DATACB_VALID) == 0) { + ds_cbarg_get_drv_per_svc_ptr(arg, &dpsp); + if (dpsp) { + DS_DBG_VLDS(CE_NOTE, "%s: unregcb draining recvq", + __func__); + vlds_recvq_drain(dpsp); + vlds_recvq_destroy(dpsp); + ds_cbarg_set_drv_per_svc_ptr(arg, NULL); + } + } + + if ((flags & DSSF_UNREGCB_VALID) != 0) { + ds_cbarg_get_hdl(arg, &hdl); + DS_DBG_VLDS(CE_NOTE, "%s: unregcb hdl: 0x%lx", __func__, + hdl); + ds_cbarg_get_drv_info(arg, &minor); + sp = ddi_get_soft_state(vlds_statep, minor); + ASSERT(sp != NULL); + ASSERT(sp->evchan != NULL); + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP) || + nvlist_add_uint64(nvl, VLDS_HDL, hdl) || + sysevent_evc_publish(sp->evchan, EC_VLDS, + ESC_VLDS_UNREGISTER, "sun.com", "kernel", nvl, + EVCH_SLEEP)) { + cmn_err(CE_WARN, "Failed to send UNREG Callback"); + } + nvlist_free(nvl); + } +} + +static void +vlds_user_data_cb(ds_cb_arg_t arg, void *buf, size_t buflen) +{ + nvlist_t *nvl = NULL; + ds_svc_hdl_t hdl; + int minor; + void *dpsp; + vlds_state_t *sp; + uint32_t flags; + + ds_cbarg_get_flags(arg, &flags); + ASSERT((flags & DSSF_ISUSER) != 0); + + if ((flags & DSSF_DATACB_VALID) == 0) { + ds_cbarg_get_drv_per_svc_ptr(arg, &dpsp); + ASSERT(dpsp != NULL); + DS_DBG_VLDS(CE_NOTE, "%s: datacb: to recvq: buflen: %ld", + __func__, buflen); + (void) vlds_recvq_put_data(dpsp, buf, buflen); + } else { + ds_cbarg_get_hdl(arg, &hdl); + DS_DBG_VLDS(CE_NOTE, "%s: datacb: usercb: hdl: 0x%lx, " + " buflen: %ld", __func__, hdl, buflen); + ds_cbarg_get_drv_info(arg, &minor); + sp = ddi_get_soft_state(vlds_statep, minor); + ASSERT(sp != NULL); + ASSERT(sp->evchan != NULL); + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, KM_SLEEP) || + nvlist_add_uint64(nvl, VLDS_HDL, hdl) || + nvlist_add_byte_array(nvl, VLDS_DATA, buf, buflen) || + sysevent_evc_publish(sp->evchan, EC_VLDS, + ESC_VLDS_DATA, "sun.com", "kernel", nvl, EVCH_SLEEP)) { + cmn_err(CE_WARN, "Failed to send DATA Callback"); + } + } + nvlist_free(nvl); +} + +/* + * Initialize receive queue if request is from user land but + * data callback is null (implying user will be using ds_recv_msg). + */ +static void +vlds_recvq_init(vlds_svc_info_t *dpsp) +{ + dpsp->state = VLDS_RECV_OK; + mutex_init(&dpsp->recv_lock, NULL, MUTEX_DRIVER, NULL); + cv_init(&dpsp->recv_cv, NULL, CV_DRIVER, NULL); + dpsp->recv_headp = NULL; + dpsp->recv_tailp = NULL; + dpsp->recv_size = 0; +} + +static void +vlds_recvq_destroy(vlds_svc_info_t *dpsp) +{ + ASSERT(dpsp->state == VLDS_RECV_UNREG_PENDING); + ASSERT(dpsp->recv_size == 0); + ASSERT(dpsp->recv_headp == NULL); + ASSERT(dpsp->recv_tailp == NULL); + + mutex_destroy(&dpsp->recv_lock); + cv_destroy(&dpsp->recv_cv); + DS_FREE(dpsp, sizeof (vlds_svc_info_t)); +} + +static int +vlds_recvq_get_data(vlds_svc_info_t *dpsp, void *buf, size_t buflen, + size_t *msglenp, int mode) +{ + vlds_recv_hdr_t *rhp; + int rv; + size_t msglen; + + mutex_enter(&dpsp->recv_lock); + while (dpsp->recv_size == 0) { + if (dpsp->state == VLDS_RECV_UNREG_PENDING) + break; + /* + * Passing in a buflen of 0 allows user to poll for msgs. + */ + if (buflen == 0) { + mutex_exit(&dpsp->recv_lock); + *msglenp = 0; + return (EFBIG); + } + dpsp->recv_nreaders += 1; + rv = cv_wait_sig(&dpsp->recv_cv, &dpsp->recv_lock); + dpsp->recv_nreaders -= 1; + if (rv == 0) { + DS_DBG_RCVQ(CE_NOTE, "%s: signal EINTR", __func__); + mutex_exit(&dpsp->recv_lock); + return (EINTR); + } + } + if (dpsp->state == VLDS_RECV_UNREG_PENDING) { + DS_DBG_RCVQ(CE_NOTE, "%s: unreg pending", __func__); + cv_broadcast(&dpsp->recv_cv); + mutex_exit(&dpsp->recv_lock); + return (EINVAL); + } + ASSERT(dpsp->recv_headp != NULL); + rhp = dpsp->recv_headp; + + /* + * Don't transfer truncated data, return EFBIG error if user-supplied + * buffer is too small. + */ + if (rhp->datasz > buflen) { + *msglenp = rhp->datasz; + mutex_exit(&dpsp->recv_lock); + return (EFBIG); + } + if (rhp == dpsp->recv_tailp) { + dpsp->recv_headp = NULL; + dpsp->recv_tailp = NULL; + } else { + dpsp->recv_headp = rhp->next; + ASSERT(dpsp->recv_headp != NULL); + } + dpsp->recv_size -= rhp->datasz; + mutex_exit(&dpsp->recv_lock); + + msglen = rhp->datasz; + rv = ddi_copyout(rhp->data, buf, msglen, mode); + + if (rv == 0) { + DS_DBG_VLDS(CE_NOTE, "%s: user data dequeued msglen: %ld", + __func__, rhp->datasz); + DS_DUMP_MSG(DS_DBG_FLAG_VLDS, rhp->data, rhp->datasz); + } + + DS_FREE(rhp->data, rhp->datasz); + DS_FREE(rhp, sizeof (vlds_recv_hdr_t)); + + if (rv != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: copyout failed", __func__); + return (EFAULT); + } + + *msglenp = msglen; + return (0); +} + +uint64_t vlds_recv_drain_delay_time = 1 * MILLISEC; + +static void +vlds_recvq_drain(vlds_svc_info_t *dpsp) +{ + vlds_recv_hdr_t *rhp, *nextp; + + mutex_enter(&dpsp->recv_lock); + dpsp->state = VLDS_RECV_UNREG_PENDING; + for (rhp = dpsp->recv_tailp; rhp != NULL; rhp = nextp) { + nextp = rhp->next; + DS_FREE(rhp->data, rhp->datasz); + DS_FREE(rhp, sizeof (vlds_recv_hdr_t)); + } + dpsp->recv_headp = NULL; + dpsp->recv_tailp = NULL; + dpsp->recv_size = 0; + + /* + * Make sure other readers have exited. + */ + while (dpsp->recv_nreaders > 0) { + cv_broadcast(&dpsp->recv_cv); + mutex_exit(&dpsp->recv_lock); + delay(vlds_recv_drain_delay_time); + mutex_enter(&dpsp->recv_lock); + } + + mutex_exit(&dpsp->recv_lock); +} + +static int +vlds_recvq_put_data(vlds_svc_info_t *dpsp, void *buf, size_t buflen) +{ + vlds_recv_hdr_t *rhp; + + mutex_enter(&dpsp->recv_lock); + if (dpsp->state != VLDS_RECV_UNREG_PENDING) { + DS_DBG_RCVQ(CE_NOTE, "%s: user data enqueued msglen: %ld", + __func__, buflen); + DS_DUMP_MSG(DS_DBG_FLAG_RCVQ, buf, buflen); + rhp = DS_MALLOC(sizeof (vlds_recv_hdr_t)); + rhp->data = DS_MALLOC(buflen); + (void) memcpy(rhp->data, buf, buflen); + rhp->datasz = buflen; + rhp->next = NULL; + if (dpsp->recv_headp == NULL) { + dpsp->recv_headp = rhp; + dpsp->recv_tailp = rhp; + } else { + dpsp->recv_tailp->next = rhp; + dpsp->recv_tailp = rhp; + } + dpsp->recv_size += rhp->datasz; + cv_broadcast(&dpsp->recv_cv); + } + mutex_exit(&dpsp->recv_lock); + return (0); +} + +static int +vlds_recv_msg(ds_svc_hdl_t hdl, void *buf, size_t buflen, size_t *msglenp, + int mode) +{ + void *dpsp; + ds_cb_arg_t cbarg; + uint32_t flags; + int rv; + + if ((rv = ds_hdl_get_cbarg(hdl, &cbarg)) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: handle %lx not found (%d)", __func__, + hdl, rv); + return (rv); + } + ds_cbarg_get_flags(cbarg, &flags); + if ((flags & DSSF_ISUSER) == 0 || (flags & DSSF_DATACB_VALID) != 0) { + DS_DBG_VLDS(CE_NOTE, "%s: invalid flags: %x", __func__, flags); + return (EINVAL); + } + ds_cbarg_get_drv_per_svc_ptr(cbarg, &dpsp); + if (dpsp == NULL) { + DS_DBG_VLDS(CE_NOTE, "%s: recv on non-ready handle: %x", + __func__, flags); + return (ENXIO); + } + rv = vlds_recvq_get_data(dpsp, buf, buflen, msglenp, mode); + return (rv); +} diff --git a/usr/src/uts/sun4v/sys/Makefile b/usr/src/uts/sun4v/sys/Makefile index 87babddd2c..e08bd67f5a 100644 --- a/usr/src/uts/sun4v/sys/Makefile +++ b/usr/src/uts/sun4v/sys/Makefile @@ -19,10 +19,9 @@ # CDDL HEADER END # -# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -#ident "%Z%%M% %I% %E% SMI" # # uts/sun4v/sys/Makefile # @@ -91,7 +90,8 @@ HDRS= \ prom_plat.h \ qcn.h \ soft_state.h \ - traptrace.h + traptrace.h \ + vlds.h CLOSED_HDRS= \ memtestio_ni.h \ diff --git a/usr/src/uts/sun4v/sys/ds.h b/usr/src/uts/sun4v/sys/ds.h index cd5efa807f..e5f479638c 100644 --- a/usr/src/uts/sun4v/sys/ds.h +++ b/usr/src/uts/sun4v/sys/ds.h @@ -20,14 +20,13 @@ */ /* - * Copyright 2006 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DS_H #define _DS_H -#pragma ident "%Z%%M% %I% %E% SMI" /* * Domain Services Client Interface diff --git a/usr/src/uts/sun4v/sys/ds_impl.h b/usr/src/uts/sun4v/sys/ds_impl.h index 101918b903..8d0e68fbb3 100644 --- a/usr/src/uts/sun4v/sys/ds_impl.h +++ b/usr/src/uts/sun4v/sys/ds_impl.h @@ -20,19 +20,19 @@ */ /* - * Copyright 2007 Sun Microsystems, Inc. All rights reserved. + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _DS_IMPL_H #define _DS_IMPL_H -#pragma ident "%Z%%M% %I% %E% SMI" - #ifdef __cplusplus extern "C" { #endif + + /* * The Domain Services Protocol * @@ -56,7 +56,7 @@ typedef struct ds_hdr { * DS Fixed Message Types */ #define DS_INIT_REQ 0x0 /* initiate DS connection */ -#define DS_INIT_ACK 0x1 /* initiation acknowledgment */ +#define DS_INIT_ACK 0x1 /* initiation acknowledgement */ #define DS_INIT_NACK 0x2 /* initiation negative acknowledgment */ /* @@ -79,10 +79,10 @@ typedef struct ds_init_nack { * DS Message Types for Version 1.0 */ #define DS_REG_REQ 0x3 /* register a service */ -#define DS_REG_ACK 0x4 /* register acknowledgment */ +#define DS_REG_ACK 0x4 /* register acknowledgement */ #define DS_REG_NACK 0x5 /* register failed */ #define DS_UNREG 0x6 /* unregister a service */ -#define DS_UNREG_ACK 0x7 /* unregister acknowledgment */ +#define DS_UNREG_ACK 0x7 /* unregister acknowledgement */ #define DS_UNREG_NACK 0x8 /* unregister failed */ #define DS_DATA 0x9 /* data message */ #define DS_NACK 0xa /* data error */ @@ -176,15 +176,32 @@ typedef struct ds_ldc { ldc_status_t state; /* current LDC state */ } ds_ldc_t; +typedef uint64_t ds_domain_hdl_t; + +#define DS_DHDL_INVALID ((ds_domain_hdl_t)0xffffffff) + +/* port flags */ +#define DS_PORT_MUTEX_INITED 0x1 /* mutexes inited? */ + typedef struct ds_port { - kmutex_t lock; /* port lock */ + uint32_t flags; /* port flags */ + kmutex_t lock; /* port and service state lock */ + kmutex_t tx_lock; /* tx port lock */ + kmutex_t rcv_lock; /* rcv port lock */ uint64_t id; /* port id from MD */ ds_port_state_t state; /* state of the port */ ds_ver_t ver; /* DS protocol version in use */ uint32_t ver_idx; /* index of version during handshake */ ds_ldc_t ldc; /* LDC for this port */ + ds_domain_hdl_t domain_hdl; /* LDOMs domain hdl assoc. with port */ + char *domain_name; /* LDOMs domain name assoc. with port */ } ds_port_t; +#define IS_DS_PORT(port) 1 /* VBSC code compatability */ +#define PORTID(port) ((ulong_t)((port)->id)) +#define PTR_TO_LONG(ptr) ((uint64_t)(ptr)) + + /* * A DS portset is a bitmap that represents a collection of DS * ports. Each bit represent a particular port id. The current @@ -192,7 +209,9 @@ typedef struct ds_port { */ typedef uint64_t ds_portset_t; +#ifndef DS_MAX_PORTS #define DS_MAX_PORTS ((sizeof (ds_portset_t)) * 8) +#endif #define DS_MAX_PORT_ID (DS_MAX_PORTS - 1) #define DS_PORT_SET(port) (1UL << (port)) @@ -200,12 +219,25 @@ typedef uint64_t ds_portset_t; #define DS_PORTSET_ADD(set, port) ((void)((set) |= DS_PORT_SET(port))) #define DS_PORTSET_DEL(set, port) ((void)((set) &= ~DS_PORT_SET(port))) #define DS_PORTSET_ISNULL(set) ((set) == 0) +#define DS_PORTSET_SETNULL(set) ((void)((set) = 0)) #define DS_PORTSET_DUP(set1, set2) ((void)((set1) = (set2))) +#define DS_PORTSET_NOT(set1, set2) ((void)((set1) = ~(set2))) +#define DS_PORTSET_AND(set1, set2) ((void)((set1) &= (set2))) + +/* + * A DS event consists of a buffer on a port. We explictly use a link to + * enequeue/dequeue on non-Solaris environments. On Solaris we use taskq. + */ +typedef struct ds_event { + ds_port_t *port; + char *buf; + size_t buflen; +} ds_event_t; /* * LDC Information */ -#define DS_STREAM_MTU 4096 +#define DS_STREAM_MTU 4096 /* * Machine Description Constants @@ -229,36 +261,75 @@ typedef enum { DS_SVC_FREE, /* svc structure not in use */ DS_SVC_INACTIVE, /* svc not registered */ DS_SVC_REG_PENDING, /* register message sent */ - DS_SVC_ACTIVE /* register message acknowledged */ + DS_SVC_ACTIVE, /* register message acknowledged */ + DS_SVC_UNREG_PENDING /* unregister is pending */ } ds_svc_state_t; +/* ds_svc flags bits */ +#define DSSF_ISCLIENT 0x0001 /* client service */ +#define DSSF_ISUSER 0x0002 /* user land service */ +#define DSSF_REGCB_VALID 0x0004 /* ops register callback is valid */ +#define DSSF_UNREGCB_VALID 0x0008 /* ops unregister callback is valid */ +#define DSSF_DATACB_VALID 0x0010 /* ops data callback is valid */ +#define DSSF_LOOPBACK 0x0020 /* loopback */ +#define DSSF_PEND_UNREG 0x0040 /* pending unregister */ +#define DSSF_ANYCB_VALID (DSSF_REGCB_VALID | DSSF_UNREGCB_VALID | \ + DSSF_DATACB_VALID) +#define DSSF_USERFLAGS (DSSF_ISCLIENT | DSSF_ISUSER | DSSF_ANYCB_VALID) + typedef struct ds_svc { ds_capability_t cap; /* capability information */ ds_clnt_ops_t ops; /* client ops vector */ ds_svc_hdl_t hdl; /* handle assigned by DS */ + ds_svc_hdl_t svc_hdl; /* remote svc hdl if client svc */ ds_svc_state_t state; /* current service state */ ds_ver_t ver; /* svc protocol version in use */ uint_t ver_idx; /* index into client version array */ ds_port_t *port; /* port for this service */ ds_portset_t avail; /* ports available to this service */ + ds_portset_t tried; /* ports tried by this service */ + int fixed; /* is svc fixed to port */ + uint_t flags; /* service flags */ + ds_cb_arg_t uarg; /* user arg for user callbacks */ + uint_t drvi; /* driver instance */ + void *drv_psp; /* driver per svc ptr */ } ds_svc_t; +typedef struct ds_svcs { + ds_svc_t **tbl; /* ptr to table */ + kmutex_t lock; + uint_t maxsvcs; /* size of the table */ + uint_t nsvcs; /* current number of items */ +} ds_svcs_t; + #define DS_SVC_ISFREE(svc) ((svc == NULL) || (svc->state == DS_SVC_FREE)) +#ifndef DS_MAXSVCS_INIT +#define DS_MAXSVCS_INIT 32 +#endif /* - * A service handle is a 64 bit value with two pieces of information + * A service handle is a 64 bit value with three pieces of information * encoded in it. The upper 32 bits is the index into the table of - * a particular service structure. The lower 32 bits is a counter - * that is incremented each time a service structure is reused. + * a particular service structure. Bit 31 indicates whether the handle + * represents a service privider or service client. The lower 31 bits is + * a counter that is incremented each time a service structure is reused. */ #define DS_IDX_SHIFT 32 -#define DS_COUNT_MASK 0xfffffffful +#define DS_COUNT_MASK 0x7fffffffull +#define DS_HDL_ISCLIENT_BIT 0x80000000ull #define DS_ALLOC_HDL(_idx, _count) (((uint64_t)_idx << DS_IDX_SHIFT) | \ ((uint64_t)(_count + 1) & \ DS_COUNT_MASK)) #define DS_HDL2IDX(hdl) (hdl >> DS_IDX_SHIFT) #define DS_HDL2COUNT(hdl) (hdl & DS_COUNT_MASK) +#define DS_HDL_ISCLIENT(hdl) ((hdl) & DS_HDL_ISCLIENT_BIT) +#define DS_HDL_SET_ISCLIENT(hdl) ((hdl) |= DS_HDL_ISCLIENT_BIT) + +#define DS_INVALID_INSTANCE (-1) + +/* enable/disable taskq processing */ +extern boolean_t ds_enabled; /* * DS Message Logging @@ -326,6 +397,158 @@ typedef struct ds_log_entry { #define DS_IS_POOL_ENTRY(ep) (((ep) >= ds_log_entry_pool) && \ ((ep) <= &(ds_log_entry_pool[DS_LOG_NPOOL]))) +/* VBSC code compatability related defines */ + +/* VBSC malloc/free are similar to user malloc/free */ +#define DS_MALLOC(size) kmem_zalloc(size, KM_SLEEP) +#define DS_FREE(ptr, size) kmem_free(ptr, size) + +/* VBSC debug print needs newline, Solaris cmn_err doesn't */ +#define DS_EOL + +/* + * Results of checking version array with ds_vers_isvalid() + */ +typedef enum { + DS_VERS_OK, + DS_VERS_INCREASING_MAJOR_ERR, + DS_VERS_INCREASING_MINOR_ERR +} ds_vers_check_t; + +/* System specific interfaces */ +extern void ds_sys_port_init(ds_port_t *port); +extern void ds_sys_port_fini(ds_port_t *port); +extern void ds_sys_drain_events(ds_port_t *port); +extern int ds_sys_dispatch_func(void (func)(void *), void *arg); +extern void ds_sys_ldc_init(ds_port_t *port); + +/* vlds cb access to svc structure */ +void ds_cbarg_get_hdl(ds_cb_arg_t arg, ds_svc_hdl_t *hdlp); +void ds_cbarg_get_flags(ds_cb_arg_t arg, uint32_t *flagsp); +void ds_cbarg_get_drv_info(ds_cb_arg_t arg, int *drvip); +void ds_cbarg_get_drv_per_svc_ptr(ds_cb_arg_t arg, void **dpspp); +void ds_cbarg_get_domain(ds_cb_arg_t arg, ds_domain_hdl_t *dhdlp); +void ds_cbarg_get_service_id(ds_cb_arg_t arg, char **servicep); +void ds_cbarg_set_drv_per_svc_ptr(ds_cb_arg_t arg, void *dpsp); +int ds_hdl_get_cbarg(ds_svc_hdl_t hdl, ds_cb_arg_t *cbargp); +void ds_cbarg_set_cookie(ds_svc_t *svc); +int ds_is_my_hdl(ds_svc_hdl_t hdl, int instance); + +/* initialization functions */ +void ds_common_init(void); +int ds_ldc_fini(ds_port_t *port); +void ds_init_svcs_tbl(uint_t nentries); + +/* message sending functions */ +void ds_send_init_req(ds_port_t *port); +int ds_send_unreg_req(ds_svc_t *svc); + +/* walker functions */ +typedef int (*svc_cb_t)(ds_svc_t *svc, void *arg); +int ds_walk_svcs(svc_cb_t svc_cb, void *arg); +int ds_svc_ismatch(ds_svc_t *svc, void *arg); +int ds_svc_free(ds_svc_t *svc, void *arg); +int ds_svc_register(ds_svc_t *svc, void *arg); + +/* service utilities */ +ds_svc_t *ds_alloc_svc(void); +ds_svc_t *ds_sys_find_svc_by_id_port(char *svc_id, ds_port_t *port, + int is_client); +ds_svc_t *ds_get_svc(ds_svc_hdl_t hdl); + +/* port utilities */ +void ds_port_common_init(ds_port_t *port); +void ds_port_common_fini(ds_port_t *port, int is_fini); + +/* misc utilities */ +ds_vers_check_t ds_vers_isvalid(ds_ver_t *vers, int nvers); +char *ds_errno_to_str(int ds_errno, char *ebuf); +char *ds_strdup(char *str); +boolean_t negotiate_version(int num_versions, ds_ver_t *sup_versionsp, + uint16_t req_major, uint16_t *new_majorp, uint16_t *new_minorp); + +/* log functions */ +int ds_log_add_msg(int32_t dest, uint8_t *msg, size_t sz); + +/* vlds driver interfaces to ds module */ +int ds_ucap_init(ds_capability_t *cap, ds_clnt_ops_t *ops, uint_t flags, + int instance, ds_svc_hdl_t *hdlp); +int ds_unreg_hdl(ds_svc_hdl_t hdl); +int ds_hdl_lookup(char *service, uint_t is_client, ds_svc_hdl_t *hdlp, + uint_t maxhdls, uint_t *nhdlsp); +int ds_service_lookup(ds_svc_hdl_t hdl, char **servicep, uint_t *is_client); +int ds_domain_lookup(ds_svc_hdl_t hdl, ds_domain_hdl_t *dhdlp); +int ds_hdl_isready(ds_svc_hdl_t hdl, uint_t *is_ready); +void ds_unreg_all(int instance); +int ds_dom_name_to_hdl(char *domain_name, ds_domain_hdl_t *dhdlp); +int ds_dom_hdl_to_name(ds_domain_hdl_t dhdl, char **domain_namep); +int ds_add_port(uint64_t port_id, uint64_t ldc_id, ds_domain_hdl_t dhdl, + char *dom_name, int verbose); +int ds_remove_port(uint64_t portid, int is_fini); + +/* ds_ucap_init flags */ +#define DS_UCAP_CLNT 0x0 /* Service is Client */ +#define DS_UCAP_SVC 0x1 /* Service is Server */ + +/* + * Error buffer size for ds_errno_to_str + */ +#define DS_EBUFSIZE 80 + +/* + * Debugging Features + */ +#ifdef DEBUG + +#define DS_DBG_BASIC 0x001 +#define DS_DBG_FLAG_LDC 0x002 +#define DS_DBG_FLAG_LOG 0x004 +#define DS_DBG_DUMP_LDC_MSG 0x008 +#define DS_DBG_FLAG_MD 0x010 +#define DS_DBG_FLAG_USR 0x020 +#define DS_DBG_FLAG_VLDS 0x040 +#define DS_DBG_FLAG_PRCL 0x080 +#define DS_DBG_FLAG_RCVQ 0x100 +#define DS_DBG_FLAG_LOOP 0x200 + +#define DS_DBG if (ds_debug & DS_DBG_BASIC) cmn_err +#define DS_DBG_LDC if (ds_debug & DS_DBG_FLAG_LDC) cmn_err +#define DS_DBG_LOG if (ds_debug & DS_DBG_FLAG_LOG) cmn_err +#define DS_DBG_MD if (ds_debug & DS_DBG_FLAG_MD) cmn_err +#define DS_DBG_USR if (ds_debug & DS_DBG_FLAG_USR) cmn_err +#define DS_DBG_VLDS if (ds_debug & DS_DBG_FLAG_VLDS) cmn_err +#define DS_DBG_PRCL if (ds_debug & DS_DBG_FLAG_PRCL) cmn_err +#define DS_DBG_RCVQ if (ds_debug & DS_DBG_FLAG_RCVQ) cmn_err +#define DS_DBG_LOOP if (ds_debug & DS_DBG_FLAG_LOOP) cmn_err + +#define DS_DUMP_MSG(flags, buf, len) if (ds_debug & (flags)) \ + ds_dump_msg(buf, len) + +extern uint_t ds_debug; +void ds_dump_msg(void *buf, size_t len); + +#define DS_BADHDL1 (ds_svc_hdl_t)(0xdeadbed1deadbed1ull) +#define DS_BADHDL2 (ds_svc_hdl_t)(0x2deadbed2deadbedull) + +#else /* DEBUG */ + +#define DS_DBG if (0) cmn_err +#define DS_DBG_LDC DS_DBG +#define DS_DBG_LOG DS_DBG +#define DS_DBG_MD DS_DBG +#define DS_DBG_USR DS_DBG +#define DS_DBG_VLDS DS_DBG +#define DS_DBG_PRCL DS_DBG +#define DS_DBG_RCVQ DS_DBG +#define DS_DBG_LOOP DS_DBG +#define DS_DUMP_MSG(flags, buf, len) +#define DS_DUMP_LDC_MSG(buf, len) + +#define DS_BADHDL1 NULL +#define DS_BADHDL2 NULL + +#endif /* DEBUG */ + #ifdef __cplusplus } #endif diff --git a/usr/src/uts/sun4v/sys/vlds.h b/usr/src/uts/sun4v/sys/vlds.h new file mode 100644 index 0000000000..3ad53ca7d3 --- /dev/null +++ b/usr/src/uts/sun4v/sys/vlds.h @@ -0,0 +1,245 @@ +/* + * 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. + */ + +#ifndef _SYS_VLDS_H_ +#define _SYS_VLDS_H_ + +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * LDOMS Domain Services Device Driver + */ + +/* + * ioctl info for vlds device + */ + +#define VLDSIOC ('d' << 16 | 's' << 8) + +#define VLDS_SVC_REG (VLDSIOC | 1) /* Register DS Service */ +#define VLDS_UNREG_HDL (VLDSIOC | 2) /* Unregister DS Service by Handle */ +#define VLDS_HDL_LOOKUP (VLDSIOC | 3) /* Lookup DS Handle(s) by Service id */ +#define VLDS_DMN_LOOKUP (VLDSIOC | 4) /* Lookup DS Domain id by Handle */ +#define VLDS_SEND_MSG (VLDSIOC | 5) /* Send DS Message by Handle */ +#define VLDS_RECV_MSG (VLDSIOC | 6) /* Receive DS Message by Handle */ +#define VLDS_HDL_ISREADY (VLDSIOC | 7) /* Handle ready for data transfers */ +#define VLDS_DOM_NAM2HDL (VLDSIOC | 8) /* Domain Name to Handle translation */ +#define VLDS_DOM_HDL2NAM (VLDSIOC | 9) /* Handle ready for data transfers */ + +/* vlds_reg_flags */ +#define VLDS_REG_CLIENT 0x01 /* Register as client */ +#define VLDS_REGCB_VALID 0x02 /* User supplied Register callback */ +#define VLDS_UNREGCB_VALID 0x04 /* User supplied Unregister callback */ +#define VLDS_DATACB_VALID 0x08 /* User supplied Data callback */ +#define VLDS_ANYCB_VALID (VLDS_REGCB_VALID | VLDS_UNREGCB_VALID | \ + VLDS_DATACB_VALID) + +#define VLDS_MAX_VERS 20 /* Max no. of vlds_ver_t entries */ + +/* + * The following are declared so that they are size-invariant. + */ + +/* String arguments to ioctl */ +typedef struct vlds_string_arg { + uint64_t vlds_strp; + uint64_t vlds_strlen; +} vlds_string_t; + +/* Version array (used by VLDS_SVC_REG) */ +typedef struct vlds_ver { + uint16_t vlds_major; + uint16_t vlds_minor; +} vlds_ver_t; + +/* Capability structure (used by VLDS_SVC_REG) */ +typedef struct vlds_cap { + vlds_string_t vlds_service; + uint64_t vlds_nver; + uint64_t vlds_versp; +} vlds_cap_t; + +/* + * VLDS_SVC_REG + */ +typedef struct vlds_svc_reg_arg { + uint64_t vlds_hdlp; /* DS Service Handle ptr. (returned) */ + uint64_t vlds_capp; /* DS Capability Structure ptr. */ + uint64_t vlds_reg_flags; /* DS reg flags */ +} vlds_svc_reg_arg_t; + +/* + * VLDS_UNREG_HDL + */ +typedef struct vlds_unreg_hdl_arg { + uint64_t vlds_hdl; /* DS Service Handle */ +} vlds_unreg_hdl_arg_t; + +/* + * VLDS_DMN_LOOKUP + */ +typedef struct vlds_dmn_lookup_arg { + uint64_t vlds_hdl; /* DS Service Handle */ + uint64_t vlds_dhdlp; /* DS Domain hdl ptr. (returned) */ +} vlds_dmn_lookup_arg_t; + +/* + * VLDS_HDL_LOOKUP + */ +typedef struct vlds_hdl_lookup_arg { + vlds_string_t vlds_service; /* DS Service Name */ + uint64_t vlds_isclient; /* DS Client flag */ + uint64_t vlds_hdlsp; /* DS Handle array ptr */ + uint64_t vlds_maxhdls; /* DS Max no. of hdls to return */ + uint64_t vlds_nhdlsp; /* DS No. of hdls returned */ +} vlds_hdl_lookup_arg_t; + +/* + * VLDS_SEND_MSG + */ +typedef struct vlds_send_msg_arg { + uint64_t vlds_hdl; /* DS Service Handle */ + uint64_t vlds_bufp; /* buffer */ + uint64_t vlds_buflen; /* message length/buffer size */ +} vlds_send_msg_arg_t; + +/* + * VLDS_RECV_MSG + */ +typedef struct vlds_recv_msg_arg { + uint64_t vlds_hdl; /* DS Service Handle */ + uint64_t vlds_bufp; /* buffer */ + uint64_t vlds_buflen; /* message length/buffer size */ + uint64_t vlds_msglenp; /* ptr to returned message length */ +} vlds_recv_msg_arg_t; + +/* + * VLDS_HDL_ISREADY + */ +typedef struct vlds_hdl_isready_arg { + uint64_t vlds_hdl; /* DS Service Handle */ + uint64_t vlds_isreadyp; /* Ptr to isready flag */ +} vlds_hdl_isready_arg_t; + +/* + * VLDS_DOM_NAM2HDL + */ +typedef struct vlds_dom_nam2hdl_arg { + vlds_string_t vlds_domain_name; /* Domain Name string */ + uint64_t vlds_dhdlp; /* ptr to returned Domain Handle */ +} vlds_dom_nam2hdl_arg_t; + +/* + * VLDS_DOM_HDL2NAM + */ +typedef struct vlds_dom_hdl2nam_arg { + uint64_t vlds_dhdl; /* Domain Handle */ + vlds_string_t vlds_domain_name; /* returned Domain Name string */ +} vlds_dom_hdl2nam_arg_t; + +/* + * Machine Description Constants for vlds driver. + */ +#define VLDS_MD_ROOT_NODE_NAME "domain-services" +#define VLDS_MD_PORT_NODE_NAME "domain-services-port" + +/* + * VLDS Sysevent defines. + * VLDS System Event Channel names are of the form: + * sun.com:vlds:pid<pid_number> + */ +#define VLDS_SYSEV_CHAN_FMT "sun.com:vlds:pid%06d" +#define VLDS_SYSEV_MAX_CHAN_NAME 32 + +#define EC_VLDS "EC_vlds" /* LDOMS Domain Services event class */ + +/* + * EC_VLDS subclass definitions - supporting attributes (name/value pairs) are + * found in sys/sysevent/vlds.h + */ +#define ESC_VLDS_REGISTER "ESC_VLDS_register" +#define ESC_VLDS_UNREGISTER "ESC_VLDS_unregister" +#define ESC_VLDS_DATA "ESC_VLDS_data" + +/* + * Event type EC_VLDS + * Event Class - EC_VLDS + * Event Sub-Class - ESC_VLDS_REGISTER + * Event Publisher - SUNW:kern:[ds_module_name] + * Attribute Name - VLDS_HDL + * Attribute Type - SE_DATA_TYPE_UINT64 + * Attribute Value - [Domain Service Handle] + * Attribute Name - VLDS_VER_MAJOR + * Attribute Type - SE_DATA_TYPE_UINT16 + * Attribute Value - [major version of the DS interface] + * Attribute Name - VLDS_VER_MINOR + * Attribute Type - SE_DATA_TYPE_UINT16 + * Attribute Value - [minor version of the DS interface] + * Attribute Name - VLDS_DOMAIN_HDL + * Attribute Type - SE_DATA_TYPE_UINT64 + * Attribute Value - [Domain handle of registered service] + * Attribute Name - VLDS_SERVICE_ID + * Attribute Type - SE_DATA_TYPE_STRING + * Attribute Value - [Service name of registered service] + * Attribute Name - VLDS_ISCLIENT + * Attribute Type - SE_DATA_TYPE_BOOLEAN_VALUE + * Attribute Value - [Service is client or provider] + * + * Event Class - EC_VLDS + * Event Sub-Class - ESC_VLDS_UNREGISTER + * Event Publisher - SUNW:kern:[ds_module_name] + * Attribute Name - VLDS_HDL + * Attribute Type - SE_DATA_TYPE_UINT64 + * Attribute Value - [Domain Service Handle] + * + * Event Class - EC_VLDS + * Event Sub-Class - ESC_VLDS_DATA + * Event Publisher - SUNW:kern:[ds_module_name] + * Attribute Name - VLDS_HDL + * Attribute Type - SE_DATA_TYPE_UINT64 + * Attribute Value - [Domain Service Handle] + * Attribute Name - VLDS_DATA + * Attribute Type - SE_DATA_TYPE_BYTE_ARRAY + * Attribute Value - [Data array passed to user] + */ + +#define VLDS_HDL "vlds_hdl" /* service handle */ +#define VLDS_VER_MAJOR "vlds_ver_major" /* major version */ +#define VLDS_VER_MINOR "vlds_ver_minor" /* minor version */ +#define VLDS_DOMAIN_HDL "vlds_domain_hdl" /* domain handle */ +#define VLDS_SERVICE_ID "vlds_service_id" /* service id */ +#define VLDS_ISCLIENT "vlds_isclient" /* service is client */ +#define VLDS_DATA "vlds_data" /* data buffer */ + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_VLDS_H_ */ diff --git a/usr/src/uts/sun4v/vlds/Makefile b/usr/src/uts/sun4v/vlds/Makefile new file mode 100644 index 0000000000..6c10e8c9ea --- /dev/null +++ b/usr/src/uts/sun4v/vlds/Makefile @@ -0,0 +1,97 @@ +# +# 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. +# +# +# This makefile drives the production of the pseudo device +# to access the sun4v PRI +# +# sun4v implementation architecture dependent +# + +# +# Path to the base of the uts directory tree (usually /usr/src/uts). +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = vlds +OBJECTS = $(VLDS_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(VLDS_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_PSM_DRV_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/sun4v/Makefile.sun4v + +# +# Override defaults to build a unique, local modstubs.o. +# +MODSTUBS_DIR = $(OBJS_DIR) + +CLEANFILES += $(MODSTUBS_O) + +# +# Define targets +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# lint pass one enforcement +# +CFLAGS += -v + +# +# Module Dependencies +LDFLAGS += -dy -Nmisc/ds -Nmisc/platsvc + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/$(PLATFORM)/Makefile.targ |