/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
/*
* Domain Services Module Common Code.
*
* This module is intended to be used by both Solaris and the VBSC
* module.
*/
#include <sys/mach_descrip.h>
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
/*
* 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 SP port id */
/*
* 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.
*/
/*
* Flag to prevent callbacks while in the middle of DS teardown.
*/
/*
* Retry count and delay for LDC reads and writes
*/
#ifndef DS_DEFAULT_RETRIES
#endif
#ifndef DS_DEFAULT_DELAY
#endif
/*
* 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.
*/
/* incoming message handling functions */
/*
* 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
*/
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 */
/* event processing functions */
static void ds_handle_recv(void *arg);
static void ds_dispatch_event(void *arg);
/* message sending functions */
/* walker functions */
/* service utilities */
/* port utilities */
/* misc utilities */
/* debug */
/* loopback */
/* client handling */
char *
{
char *newstr;
return (newstr);
}
void
ds_common_init(void)
{
/* Validate version table */
/* Initialize services table */
/* enable callback processing */
ds_enabled = B_TRUE;
}
/* BEGIN LDC SUPPORT FUNCTIONS */
static char *
{
buf[0] = 0;
return (buf);
}
static ldc_status_t
{
int rv;
/*
* Read status and update ldc state info in port structure.
*/
} else {
}
return (ldc_state);
}
static void
{
__func__);
(void) ds_update_ldc_state(port);
/* reset the port state */
/* acknowledge the reset */
}
static void
{
__func__);
/*
* Initiate the handshake.
*/
}
}
static uint_t
{
if (!ds_enabled) {
return (LDC_SUCCESS);
}
goto done;
}
if (event & LDC_EVT_UP) {
}
if (event & LDC_EVT_READ) {
goto done;
}
}
}
if (event & LDC_EVT_WRITE) {
}
}
done:
return (LDC_SUCCESS);
}
static int
{
int rv;
return (rv);
}
if (rv != 0) {
return (rv);
}
return (0);
}
int
{
int rv;
return (rv);
}
return (rv);
}
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
{
int rv = 0;
int retry_count = 0;
*sizep = 0;
while (bytes_left > 0) {
nbytes = bytes_left;
} else
if (rv != 0) {
if (rv == ECONNRESET) {
break;
break;
}
} else {
if (nbytes != 0) {
"read %ld bytes, %d retries" DS_EOL,
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) {
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) {
break;
}
}
return (rv);
}
static void
{
char *hbuf;
char *currp;
int rv;
/*
* 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.
*/
for (;;) {
} else
break;
/*
* Read in the next message.
*/
/* read in the message header */
break;
}
/*
* A zero length read is a valid signal that
* there is no data left on the channel.
*/
if (read_size != 0) {
"length, received %ld bytes, expected %ld"
}
continue;
}
/* get payload size and allocate a buffer */
if (!msg) {
continue;
}
/* move message header into buffer */
/* read in the message body */
break;
}
/* validate the size of the message */
"received %ld bytes, expected %ld" DS_EOL,
msglen);
continue;
}
/*
* Send the message for processing, and store it
* in the log. The memory is deallocated only when
* the message is removed from the log.
*/
/* log the message */
}
}
/* handle connection reset errors returned from ds_recv_msg */
if (rv == ECONNRESET) {
}
}
static void
{
} else {
}
}
int
{
int rv;
int loopcnt = 0;
/*
* 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.
*/
do {
} else
if (rv != 0) {
if (rv == ECONNRESET) {
(void) ds_sys_dispatch_func((void (*)(void *))
return (rv);
} else if ((rv == EWOULDBLOCK) &&
(loopcnt++ < ds_retries)) {
} else {
"ldc_write failed (%d), %d bytes "
(int)amt_left);
goto error;
}
} else {
loopcnt = 0;
}
} while (amt_left > 0);
return (rv);
}
/* END LDC SUPPORT FUNCTIONS */
/* BEGIN DS PROTOCOL SUPPORT FUNCTIONS */
static void
{
char *msg;
/* sanity check the incoming message */
explen);
return;
}
/*
* 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) {
} else {
}
/*
* Send the response
*/
if (match) {
}
}
static void
{
/* sanity check the incoming message */
explen);
return;
}
return;
}
return;
}
}
static void
{
int idx;
/* sanity check the incoming message */
explen);
return;
}
return;
}
if (nack->major_vers == 0) {
/* no supported protocol version */
return;
}
/*
* Walk the version list, looking for a major version
* that is as close to the requested major version as
* possible.
*/
/* found a version to try */
goto done;
}
}
if (idx == DS_NUM_VER) {
/* no supported version */
return;
}
done:
/* start the handshake again */
}
static ds_svc_t *
{
int idx;
/* walk every table entry */
if (DS_SVC_ISFREE(svc))
continue;
continue;
continue;
return (svc);
} else if (!found_svc) {
}
}
return (found_svc);
}
static void
{
char *msg;
/* sanity check the incoming message */
"length (%ld), expected at least %ld" DS_EOL,
return;
}
nack->major_vers = 0;
/*
* Send the response
*/
return;
}
/*
* 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). We
* expect the service provider to be in the inactive (DS_SVC_INACTIVE)
* state. If the service provider has already sent out a reg req (the
* state is DS_SVC_REG_PENDING) or has already handshaken (the
* state is DS_SVC_ACTIVE), then we can simply ignore this reg
* req. For any other state, we force an unregister before initiating
* a reg req.
*/
case DS_SVC_REG_PENDING:
case DS_SVC_ACTIVE:
return;
case DS_SVC_INACTIVE:
break;
default:
"client forced unreg, state (%x)" DS_EOL,
break;
}
return;
}
/*
* Only remote service providers can initiate a registration. The
* local sevice from here must be a client service.
*/
/*
* 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) {
/*
* If the current local service is already in use and
* it's not on this port, clone it.
*/
/*
* Someone probably dropped an unreg req
* somewhere. Force a local unreg.
*/
/*
* 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 {
}
}
/* Call the registration callback */
}
} else {
}
/* send message */
}
static void
{
/* sanity check the incoming message */
explen);
return;
}
/*
* This searches for service based on how we generate handles
* and so only works because this is a reg ack.
*/
goto done;
}
/* make sure the message makes sense */
goto done;
}
/* major version has been agreed upon */
/*
* Use the minor version specified in the
* original request.
*/
} else {
/*
* Use the lower minor version returned in
* the ack. By defninition, all lower minor
* versions must be supported.
*/
}
/* notify the client that registration is complete */
/*
* Use a temporary version structure so that
* the copy in the svc structure cannot be
* modified by the client.
*/
}
done:
}
static boolean_t
{
return (is_ready);
}
static void
{
int i;
/*
* Get the ports that haven't been tried yet and are available to try.
*/
for (i = 0; i < DS_MAX_PORTS; i++) {
DS_PORTSET_DEL(totry, i);
}
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.
*/
continue;
}
if (!ds_port_is_ready(port))
continue;
/* register sent successfully */
break;
}
/* reset the service to try the next port */
}
}
static void
{
int idx;
/* sanity check the incoming message */
explen);
return;
}
/*
* We expect a reg_nack for a client ping.
*/
goto done;
}
/*
* This searches for service based on how we generate handles
* and so only works because this is a reg nack.
*/
goto done;
}
/* make sure the message makes sense */
goto done;
}
goto done;
}
/*
* A major version of zero indicates that the
* service is not supported at all.
*/
if (nack->major_vers == 0) {
goto done;
}
/*
* Walk the version list for the service, looking for
* a major version that is as close to the requested
* major version as possible.
*/
/* found a version to try */
break;
}
}
/* no supported version */
goto done;
}
/* start the handshake again */
done:
}
static void
{
char *msg;
/* sanity check the incoming message */
explen);
return;
}
/* lookup appropriate client or service */
if (!is_up)
return;
return;
}
/* send message */
}
static void
{
/* sanity check the incoming message */
explen);
return;
}
#ifdef DEBUG
/*
* 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.
*/
}
#endif /* DEBUG */
}
static void
{
/* sanity check the incoming message */
explen);
return;
}
#ifdef DEBUG
/*
* 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.
*/
}
#endif /* DEBUG */
}
static void
{
char *msg;
int msgsz;
int hdrsz;
/* sanity check the incoming message */
explen);
return;
}
/* strip off the header for the client */
== NULL) {
return;
}
}
/* dispatch this message to the client */
}
static void
{
/* sanity check the incoming message */
explen);
return;
}
return;
}
}
}
}
/* Initialize the port */
void
{
return;
}
/*
* make sure no one has changed the state under us before
* we update the state.
*/
}
}
static int
{
int rv;
return (-1);
}
/* check on the LDC to Zeus */
/* can not send message */
return (-1);
}
/* make sure port is ready */
/* can not send message */
return (-1);
}
/* allocate the message buffer */
/* copy in the header data */
/* copy in the service id */
/* send the message */
rv = -1;
}
return (rv);
}
/*
* Keep around in case we want this later
*/
int
{
int rv;
return (-1);
}
/* check on the LDC to Zeus */
/* can not send message */
return (-1);
}
/* make sure port is ready */
/* can not send message */
return (-1);
}
/* copy in the header data */
} else {
}
/* send the message */
rv = -1;
}
return (rv);
}
static void
{
/* check on the LDC to Zeus */
/* can not send message */
return;
}
/* make sure port is ready */
/* can not send message */
return;
}
/* copy in the header data */
/* send the message */
}
static void
{
/* check on the LDC to Zeus */
/* can not send message */
return;
}
/* make sure port is ready */
/* can not send message */
return;
}
/* copy in the header data */
/* send the message */
}
/* END DS PROTOCOL SUPPORT FUNCTIONS */
#ifdef DEBUG
/*
* 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
{
int i, j;
char *curr;
char *aoff;
if (len > 128)
len = 128;
/* walk the buffer one line at a time */
for (i = 0; i < len; i += BYTESPERLINE) {
/*
* 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++) {
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.
*/
*curr++ = ' ';
}
}
#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
{
int idx;
/* walk every table entry */
/* execute the callback */
break;
}
return (idx);
}
static int
{
/*
* 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
{
if (DS_SVC_ISFREE(svc)) {
return (0);
}
/* found a match */
return (1);
}
return (0);
}
int
{
if (DS_SVC_ISFREE(svc)) {
return (0);
}
/* found a match */
return (1);
}
return (0);
}
int
{
return (0);
}
}
}
return (0);
}
static void
{
int idx;
/* walk every table entry */
}
}
static int
{
if (DS_SVC_ISFREE(svc))
return (0);
return (0);
return (0);
if (!ds_port_is_ready(port))
return (0);
return (0);
} else {
/*
* Never send a client reg req to the SP.
*/
return (0);
}
}
/* register sent successfully */
return (1);
}
/* reset the service */
}
return (0);
}
static int
{
if (DS_SVC_ISFREE(svc))
return (0);
return (0);
}
int
{
int idx;
if (DS_SVC_ISFREE(svc))
return (0);
}
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.
*/
/*
* If the port is not in the available list,
* it is not a candidate for registration.
*/
continue;
}
break;
}
}
return (0);
}
static int
{
if (DS_SVC_ISFREE(svc)) {
return (0);
}
/* make sure the service is using this port */
return (0);
}
if (port) {
} else {
}
/* reset the service structure */
/* call the client unregister callback */
}
/* increment the count in the handle to prevent reuse */
}
/* try to initiate a new registration */
}
return (0);
}
static int
{
if (DS_SVC_ISFREE(svc)) {
/* nothing to do */
return (0);
}
return (0);
}
static void
{
if (!was_ready) {
}
if (!was_ready) {
/*
* 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.
*/
}
}
ds_svc_t *
ds_alloc_svc(void)
{
int idx;
goto found;
}
/*
* There was no free space in the table. Grow
* the table to double its current size.
*/
/* copy old table data to the new table */
/* clean up the old table */
/* search for a free space again */
/* the table is locked so should find a free slot */
/* allocate a new svc structure if necessary */
/* allocate a new service */
}
/* fill in the handle */
return (newsvc);
}
static void
{
if (port) {
}
}
ds_svc_t *
{
int idx;
if (hdl == DS_INVALID_HDL)
return (NULL);
/* check if index is out of bounds */
return (NULL);
/* check for a valid service */
if (DS_SVC_ISFREE(svc))
return (NULL);
/* make sure the handle is an exact match */
return (NULL);
return (svc);
}
static void
{
/* connection went down, mark everything inactive */
}
/*
* Verify that a version array is sorted as expected for the
* version negotiation to work correctly.
*/
{
int idx;
/*
* Walk the version array, verifying correct ordering.
* The array must be sorted from highest supported
* version to lowest supported version.
*/
" increasing major versions" DS_EOL);
return (DS_VERS_INCREASING_MAJOR_ERR);
}
continue;
}
" increasing minor versions" DS_EOL);
return (DS_VERS_INCREASING_MINOR_ERR);
}
}
return (DS_VERS_OK);
}
/*
* Extended user capability init.
*/
int
{
int rv = 0;
int is_loopback;
int is_client;
/* sanity check the args */
return (EINVAL);
}
/* sanity check the capability specifier */
__func__);
return (EINVAL);
}
/* sanity check the version array */
"increasing major versions" :
"increasing minor versions");
return (EINVAL);
}
/* data and register callbacks are required */
return (EINVAL);
}
flags &= DSSF_USERFLAGS;
/* check if the service is already registered */
/* already registered */
return (EALREADY);
}
svc = ds_alloc_svc();
if (is_client) {
}
/*
* Check for loopback. "pri" is a legacy service that assumes it
* will never use loopback mode.
*/
is_loopback = 0;
== 1) {
return (rv);
}
is_loopback = 1;
} else
is_loopback = 0;
/* copy over all the client information */
/* make a copy of the service name */
/* make a copy of the version array */
/* copy the client ops vector */
/*
* 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) {
}
if (is_loopback) {
}
/*
* If this is a client or a non-loopback service provider, send
* out register requests.
*/
if (hdlp) {
}
return (0);
}
/*
* ds_cap_init interface for previous revision.
*/
int
{
}
/*
* Interface for ds_unreg_hdl in lds driver.
*/
int
{
int is_loopback;
(u_longlong_t)hdl);
return (ENXIO);
}
(void) ds_send_unreg_req(svc);
}
if (is_loopback) {
}
return (0);
}
int
{
int rv;
return (rv);
}
if (nhdls == 0) {
return (ENXIO);
}
rv);
return (rv);
}
rv);
return (rv);
}
return (0);
}
int
{
int rv;
int is_client = 0;
(u_longlong_t)hdl);
return (ENXIO);
}
/* channel is up, but svc is not registered */
return (ENOTCONN);
}
return (0);
}
return (ECONNRESET);
}
is_client = 1;
}
/* check that the LDC channel is ready */
return (ECONNRESET);
}
if (is_client) {
} else {
}
}
}
return (rv);
}
void
{
int rv;
}
/*
* If LDC successfully init'ed, try to kick off protocol for this port.
*/
if (rv == 0) {
}
}
void
{
}
/*
* Initialize table of registered service classes
*/
void
{
int tblsz;
}
/*
* Find the max and min version supported.
* Hacked from zeus workspace, support.c
*/
static void
{
int i;
for (i = 1; i < num_versions; i++) {
}
}
/*
* 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
*/
{
int i;
/*
* If the minimum version supported is greater than
* the version requested, return the lowest version
* supported
*/
*new_majorp = min_major;
return (B_FALSE);
}
/*
* If the largest version supported is lower than
* the version requested, return the largest version
* supported
*/
*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.
*/
for (i = 0; i < num_versions; i++) {
*new_majorp = req_major;
break;
} else {
lower_major = major;
}
}
/*
* If no match is found, return the closest available number
*/
if (!found_match)
return (found_match);
}
/*
*/
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 *
{
int i, en;
return (ebuf);
}
}
return (ebuf);
}
static void
{
(u_longlong_t)hdl);
(u_longlong_t)hdl);
return;
}
}
}
static void
{
(u_longlong_t)hdl);
return;
}
(u_longlong_t)hdl);
}
}
static void
{
(u_longlong_t)hdl);
return;
}
(u_longlong_t)hdl);
}
}
static int
{
int i;
int match = 0;
return (ENXIO);
}
/* negotiate a version between loopback services, if possible */
}
if (!match) {
return (ENOTSUP);
}
/*
* If a client service is not inactive, clone it. If the service is
* not a client service and has a reg req pending (usually from OBP
* since there are never multiple service clients. Also reg req pending
* only happens for non-client services, so it's OK to skip
* this block that does client service cloning.
*/
return (EBUSY);
}
}
return (0);
}
static ds_svc_t *
{
int idx;
/* walk every table entry */
if (DS_SVC_ISFREE(svc))
continue;
return (svc);
}
}
return (NULL);
}
static ds_svc_t *
{
newsvc = ds_alloc_svc();
/* Can only clone clients for now */
(u_longlong_t)hdl);
/*
* 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.
*/
}
return (newsvc);
}
/*
* Internal handle lookup function.
*/
static int
{
int idx;
int nhdls = 0;
if (DS_SVC_ISFREE(svc))
continue;
nhdls++;
} else {
nhdls++;
}
}
}
return (nhdls);
}
/*
* Interface for ds_hdl_lookup in lds driver.
*/
int
{
return (0);
}
/*
* 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
{
}
}
static void
{
/*
* Clear out the structure, but do not deallocate the
* memory. It can be reused for the next registration.
*/
/* save the handle to prevent reuse */
/* initialize for next use */
}