etm_xport_api_dd.c revision 31e37bb439502e3f7c4c0a9a77d655ea5d56887a
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* etm_xport_api_dd.c FMA ETM-to-Transport API implementation
*
* library for establishing connections and transporting FMA events
* between ETMs (event transport modules) in separate fault domain,
* ie, between domain and service processor in same chassis, using
* a character device driver based transport
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* --------------------------------- includes --------------------------------
*/
#include <pthread.h>
#include <stdio.h>
#include <stropts.h>
#include <locale.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <alloca.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <poll.h>
#include "etm_xport_api.h"
#include "etm_etm_proto.h"
#include "etm_impl.h"
/*
* ----------------------- private consts and defns --------------------------
*/
/* magic numbers (32 bits) for transport address and connection handle */
#define ETM_XPORT_DD_MAGIC_ADDR (0x45544D41)
#define ETM_XPORT_DD_MAGIC_CONN (0x45544D43)
/* flags to use in opening transport device */
/*
* transport address and connection handle structures overload fn and fd
* fields to include state information:
*
* fn file name NULL means unused or closed
* fd file descriptor -1 means unused or closed
*/
typedef struct _etm_xport_addr {
char *fn; /* fullpath to device node */
typedef struct _etm_xport_conn {
int fd; /* open dev file descriptor */
/*
* filename of device node to reach SP from domain. one of these two
* device nodes will be used:
* ETM_XPORT_DEV_FN_SP - the Ontario glvc
* ETM_XPORT_DEV_VLDC - the more recent LDOMS 1.0 (a.k.a. Ontario+) vldc
* When the latter is in use, use_vldc is set to 1.
*
* filenames of device nodes to reach domains from SP
* are NA because SP runs ALOM vs Solaris or Linux
* and ETM is for Unix based OSes
*/
#define ETM_XPORT_DEV_FN_SP "/dev/spfma"
#define ETM_XPORT_DEV_VLDC \
"/devices/virtual-devices@100/channel-devices@200" \
"/virtual-channel-client@2:spfma"
/*
* -------------------------- global variables -------------------------------
*/
static int use_vldc = 0;
static struct stats {
/* address handle failures */
/* connection handle failures */
/* xport API failures */
/* system and library failures */
} etm_xport_stats = {
/* address handle failures */
{ "xport_addr_magicnum_bad", FMD_TYPE_UINT64,
"invalid address handle magic number" },
{ "xport_addr_fn_bad", FMD_TYPE_UINT64,
"invalid address handle file name" },
/* connection handle failures */
{ "xport_conn_magicnum_bad", FMD_TYPE_UINT64,
"invalid connection handle magic number" },
{ "xport_conn_fd_bad", FMD_TYPE_UINT64,
"invalid connection handle file descriptor" },
{ "xport_buffread_badargs", FMD_TYPE_UINT64,
"bad arguments in etm_xport_buffered_read" },
{ "xport_rawpeek_badargs", FMD_TYPE_UINT64,
"bad arguments in etm_xport_raw_peek" },
/* xport API failures */
{ "xport_accept_badargs", FMD_TYPE_UINT64,
"bad arguments in etm_xport_accept" },
{ "xport_get_addr_conn_badargs", FMD_TYPE_UINT64,
"bad arguments in etm_xport_get_addr_conn" },
{ "xport_free_addr_badargs", FMD_TYPE_UINT64,
"bad arguments in etm_xport_free_addr" },
{ "xport_free_addrv_badargs", FMD_TYPE_UINT64,
"bad arguments in etm_xport_free_addrv" },
{ "xport_get_any_lcc_badargs", FMD_TYPE_UINT64,
"bad arguments in etm_xport_get_any_lcc" },
/* system and library failures */
{ "xport_os_open_fail", FMD_TYPE_UINT64,
"open system call failures" },
{ "xport_os_close_fail", FMD_TYPE_UINT64,
"close system call failures" },
{ "xport_os_read_fail", FMD_TYPE_UINT64,
"read system call failures" },
{ "xport_os_write_fail", FMD_TYPE_UINT64,
"write system call failures" },
{ "xport_os_peek_fail", FMD_TYPE_UINT64,
"peek (ioctl) failures" },
{ "xport_os_ioctl_fail", FMD_TYPE_UINT64,
"ioctl system call failures" }
};
/* intermediate read buffer to [partially] emulate byte stream semantics */
/*
* -------------------------- private variables ------------------------------
*/
/*
* Design_Note:
*
* Access to the transport for receiving is serialized so that if two threads
* exist, one for accepting new connections and one for reading on an
* accepted connection, they don't race with each other. A pingpong access
* deadlocks caused by locking the mutex inside accept() and open(), only
* accept() is covered with an approrpriate unlock inside close() using
* etm_xport_ser_conn to notice the proper connection and when to unlock.
*
* This could've been done within ETM [inside the pertinent threads]
* more easily; but because it's platform specific it's being done here
* within the ETM-to-Transport API.
*/
static pthread_mutex_t
etm_xport_ser_lock; /* xport access serialization lock */
static _etm_xport_conn_t *
static _etm_xport_conn_t *
static pthread_mutex_t
/* lock for open()/close() VLDC */
static int
etm_xport_debug_lvl = 0; /* debug level: 0 off, 1 on, 2 more, ... */
static char *
static int
etm_xport_should_fake_dd = 0; /* bool for whether to fake device driver */
/*
* -------------------------- private functions ------------------------------
*/
/*
* [for unit testing with device driver absent or
* for alternative directory entry based transports],
* return 0 for success
* or -1 and set errno
* caveats:
* simulation may be incomplete, especially wrt peek()
*
* Design_Note: To avoid interfering with FMD's signal mask (SIGALRM)
* do not use [Solaris] sleep(3C) and instead use
* pthread_cond_wait() or nanosleep(), both of which
* are POSIX spec-ed to leave signal masks alone.
* This is needed for Solaris and Linux (domain and SP).
*/
static int
{
int rv; /* ret val */
ssize_t n; /* gen use */
rv = 0; /* default is success */
if (op == ETM_XPORT_IOCTL_DATA_PEEK) {
peek_ctl_ptr = buf;
/* sleep until some data avail, potentially forever */
for (;;) {
rv = -1;
goto func_ret;
}
peek_ctl_ptr->pk_buflen = n;
/* return bogus data assuming content unused */
goto func_ret;
}
rv = -1;
goto func_ret;
}
} /* forever awaiting data */
} else if (op == ETM_XPORT_IOCTL_OPT_OP) {
op_ctl_ptr = buf;
/* default near MTU_SZ gets and agree with everything else */
}
goto func_ret;
} /* whether ioctl op is handled */
rv = -1;
return (rv);
} /* etm_fake_ioctl() */
/*
* etm_xport_get_fn - return a cached read-only copy
* of the device node name to use
* for the given I/O operation
*/
static char *
{
char *rv; /* ret val */
char *prop_str; /* property string */
char *cp; /* char ptr */
/* use cached copies if avail */
return (fn_wr);
}
(fn_rd[0] != '\0')) {
return (fn_rd);
}
/* create cached copies if empty "" property string */
if (etm_xport_debug_lvl >= 2) {
prop_str);
}
char *fname;
use_vldc = 1;
} else {
use_vldc = 0;
}
if (io_op == ETM_IO_OP_WR) {
}
goto func_ret;
/* create cached copies if "write[|read]" property string */
if (io_op == ETM_IO_OP_WR) {
*cp = '\0';
}
} else {
cp++;
} else {
}
if (etm_xport_debug_lvl >= 2) {
}
return (rv);
} /* etm_xport_get_fn() */
/*
* etm_xport_valid_addr - validate the given transport address,
* return 0 if valid
* or -errno value if not
*/
static int
{
return (-EINVAL);
}
return (-EFAULT);
}
/* errno assumed set by above call */
return (-errno);
}
return (0);
} /* etm_xport_valid_addr() */
/*
* etm_xport_valid_conn - validate the given connection handle,
* return 0 if valid
* or -errno value if not
*/
static int
{
return (-EINVAL);
}
return (-EFAULT);
}
return (-EBADF);
}
return (0);
} /* etm_xport_valid_conn() */
/*
* etm_xport_free_addr - free the given transport address
*/
static void
{
return;
}
} /* etm_xport_free_addr() */
/*
* etm_xport_dup_addr - duplicate the given transport address,
* which is to be freed separately,
* return the newly allocated transport address
* pending until possible to do so
*/
static etm_xport_addr_t
{
return (new_addr);
} /* etm_xport_dup_addr() */
/*
* etm_xport_raw_peek - try to peek N <= MTU bytes from the connection
* into the caller's given buffer,
* return how many bytes actually peeked
* or -errno value
*
* caveats:
* peeked data is NOT guaranteed by all platform transports
* this casts some doubt on the utility of this func
*
* transport does NOT support peek sizes > MTU
*/
static ssize_t
{
ssize_t n; /* gen use */
rv = 0;
/* sanity check args */
return (-EINVAL);
}
return (-EINVAL);
}
/* try to peek requested amt of data */
if (etm_xport_should_fake_dd) {
&peek_ctl);
} else {
}
if (n < 0) {
/* errno assumed set by above call */
} else {
}
if (etm_xport_debug_lvl >= 3) {
rv);
}
return (rv);
} /* etm_xport_raw_peek() */
/*
* Design_Note:
*
* The transport device driver did not implement byte stream semantics
* per the spec; its behavior is closer to that of a block device.
* Consequently, ETM within its Transport API attempts to make the device
* look like a byte stream by using an intermediate buffer in user space
* and maintaining progress pointers within that buffer which is populated
* in near-MTU sized reads. We think it's OK to leave the write side
* implementation as it was originally written for byte stream semantics
* because we were told subsequent write()s will pend until the earlier
* content is read() at the remote end -- essentially each write() must be
* paired with a single read() -- the device driver does not buffer any I/O.
*
* The early driver bugs of returning more data than requested (thus
* causing buffer overrun corruptions/crashes) and requiring user buffers
* to be stack based vs heap based, have both been corrected.
*/
/*
* etm_xport_buffered_read - try to read N <= MTU bytes from the connection
* or from an privately maintained intermediate buffer,
* into the caller's given buffer,
* return how many bytes actually read
* or -errno value
*
* caveats:
* simple buffer scheme consumes 2x MTU bytes of memory and
* may do unnecesssary memory copies for ease of coding
*/
static ssize_t
{
ssize_t i, n; /* gen use */
/* perform one-time initializations */
/*
* Design_Note:
*
* These initializations are not done in etm_xport_init() because
* the connection/device is not yet open and hence the MTU size
* is not yet known. However, the corresponding cleanup is done
* in etm_xport_fini(). The buffering for byte stream semantics
* should be done on a per device vs per connection basis; the
* MTU size is assumed to remain constant across all connections.
*/
if (etm_xport_irb_mtu_sz == 0) {
ETM_XPORT_OPT_MTU_SZ)) < 0) {
} else {
etm_xport_irb_mtu_sz = n;
}
}
if (etm_xport_irb_area == NULL) {
}
/* sanity check the byte count after have MTU */
if (byte_cnt > etm_xport_irb_mtu_sz) {
return (-EINVAL);
}
if (etm_xport_debug_lvl >= 2) {
byte_cnt);
}
return (byte_cnt);
}
/* slide buffer contents to front to make room for [MTU] more bytes */
/*
* peek to see how much data is avail and read all of it;
* there is no race condition between peeking and reading
* due to unbuffered design of the device driver
*/
if (use_vldc) {
return (-EIO);
/*
* set i to the maximum size --- read(..., i) below will
* pull in n bytes (n <= i) anyway
*/
i = etm_xport_irb_mtu_sz;
} else {
etm_xport_irb_mtu_sz)) < 0) {
return (i);
}
}
/* errno assumed set by above call */
return (-errno);
}
etm_xport_irb_tail += n;
/* satisfy request as best we can with what we now have */
etm_xport_irb_head += n;
if (etm_xport_debug_lvl >= 2) {
}
return (n);
} /* etm_xport_buffered_read() */
/*
* ------------------ connection establishment functions ---------------------
*/
/*
* etm_xport_init - initialize/setup any transport infrastructure
* before any connections are opened,
* return 0 or -errno value if initialization failed
*/
int
{
int i; /* vector index */
ssize_t n; /* gen use */
int rv; /* ret val */
char *fn; /* filename of dev node */
rv = 0; /* assume good */
goto func_ret;
}
/* setup statistics and properties from FMD */
sizeof (etm_xport_stats) / sizeof (fmd_stat_t),
(fmd_stat_t *)&etm_xport_stats);
/* decide whether to fake [some of] the device driver behavior */
etm_xport_should_fake_dd = 0; /* default to false */
/* errno assumed set by above call */
goto func_ret;
}
}
/* validate each default dst transport address */
/* errno assumed set by above call */
goto func_ret;
}
if ((n = etm_xport_valid_addr(_addrv[i])) < 0) {
_addrv[i]);
rv = n;
goto func_ret;
}
} /* foreach dst addr */
/* create mutex for xport access serialization */
if (use_vldc) {
if (etm_xport_vldc_conn == NULL) {
}
}
}
if (rv >= 0) {
}
return (rv);
} /* etm_xport_init() */
/*
* etm_xport_open - open a connection with the given endpoint,
* return the connection handle,
* or NULL and set errno if open failed
*
* Design_Note: The current transport device driver's open()
* call will succeed even if the SP is down;
* hence there's currently no need for a retry
* mechanism.
*/
{
ssize_t n; /* gen use */
if ((n = etm_xport_valid_addr(addr)) < 0) {
errno = (-n);
return (NULL);
}
/* allocate a connection handle and start populating it */
(void) pthread_mutex_lock(&etm_xport_vldc_lock);
ETM_XPORT_OPEN_FLAGS, 0)) == -1) {
/* errno assumed set by above call */
return (NULL);
}
}
/* Set the channel to reliable mode */
/* errno assumed set by above call */
return (NULL);
}
}
(void) pthread_mutex_unlock(&etm_xport_vldc_lock);
/* return the fully formed connection handle */
return (_conn);
} /* etm_xport_open() */
/*
* etm_xport_accept - accept a request to open a connection,
* pending until a remote endpoint opens a
* a new connection to us [and sends an ETM msg],
* per non-NULL addrp optionally indicate the
* return the connection handle,
* or NULL and set errno on failure
*
* caveats:
* any returned transport address is valid only for
* as long as the associated connection remains open;
* callers should not try to free the transport address
*
* if new connections are rapid relative to how
* frequently this function is called, fairness will
* be provided among which connections are accepted
*
* this function may maintain state to recognize [new]
*/
{
int n; /* byte cnt */
/*
* get the default dst transport address and open a connection to it;
* there is only 1 default addr
*/
/* errno assumed set by above call */
goto func_ret;
}
goto func_ret;
}
goto func_ret;
}
/* errno assumed set by above call */
goto func_ret;
}
/*
* lock mutex to avoid race condition between handler for a new
* connection and the top level connection acceptance server;
* remember the connection handle for close
*/
(void) pthread_mutex_lock(&etm_xport_ser_lock);
if (etm_xport_should_fake_dd) {
}
/*
* peek from the connection to simulate an accept() system call
* behavior; this will pend until some ETM message is written
* from the other end
*/
if (use_vldc) {
goto func_ret;
}
} else {
errno = (-n);
goto func_ret;
}
}
/* cleanup the connection if failed */
if (etm_xport_ser_conn != NULL) {
(void) pthread_mutex_unlock(&etm_xport_ser_lock);
}
}
} else {
}
}
/* free _addrv and all its transport addresses */
}
if (etm_xport_debug_lvl >= 2) {
}
return (rv);
} /* etm_xport_accept() */
/*
* etm_xport_close - close a connection from either endpoint,
* return the original connection handle,
* or NULL and set errno if close failed
*/
{
int nev; /* -errno val */
goto func_ret;
}
/* close the device node */
(void) pthread_mutex_lock(&etm_xport_vldc_lock);
/* errno assumed set by above call */
}
}
(void) pthread_mutex_unlock(&etm_xport_vldc_lock);
/*
* unlock the mutex after the device node is closed
* if this is the appropriate connection handle
*/
if (_conn == etm_xport_ser_conn) {
(void) pthread_mutex_unlock(&etm_xport_ser_lock);
}
/* cleanup the connection */
}
}
return (rv);
} /* etm_xport_close() */
/*
* etm_xport_get_ev_addrv - indicate which transport addresses
* are implied as destinations by the
* given FMA event, if given no FMA event
* (NULL) indicate default or policy
* driven dst transport addresses,
* return an allocated NULL terminated
* vector of allocated transport addresses,
* or NULL and set errno if none
* caveats:
* callers should never try to individually free an addr
* within the returned vector
*/
{
/*
*
* in reality we have just 1 dst transport addr
*/
} else {
/*
* allocate address handles per FMA event content
*
* in reality we have just 1 dst transport addr
*/
} /* whether caller passed in a FMA event */
/* allocate vector with 1 non-NULL transport addr */
return ((void *) _addrv);
} /* etm_xport_get_ev_addrv() */
/*
* etm_xport_free_addrv - free the given vector of transport addresses,
* including each transport address
*/
void
{
int i; /* vector index */
return;
}
}
} /* etm_xport_free_addrv() */
/*
* etm_xport_get_addr_conn - indicate which connections in a NULL
* terminated vector of connection
* handles are associated with the
* given transport address,
* return an allocated NULL terminated
* vector of those connection handles,
* or NULL and set errno if none
*/
{
int n; /* matching transport addr cnt */
int i; /* vector index */
return (NULL);
}
/* count, allocate space for, and copy, all matching addrs */
n = 0;
n++;
}
} /* for counting how many addresses match */
n = 0;
n++;
}
} /* for copying matching address pointers */
return ((void *) _mcv);
} /* etm_xport_get_addr_conn() */
/*
* etm_xport_get_any_lcc - indicate which endpoint has undergone
* a life cycle change and what that change
* was (ex: came up), pending until a change
* return the appropriate address handle,
* or NULL and set errno if problem
*
* caveats:
* regarding life cycle changes of endpoints
*
* if life cycle changes are rapid relative to how
* frequently this function is called, fairness will
* be provided among which endpoints are reported
*/
{
return (NULL);
}
/*
*/
return (NULL);
} /* etm_xport_get_any_lcc() */
/*
* after all connections are closed,
* return 0 or -errno value if teardown failed
*/
int
{
/* destroy the xport access serialization lock */
(void) pthread_mutex_destroy(&etm_xport_ser_lock);
}
/* free any long standing properties from FMD */
/* cleanup the intermediate read buffer */
if (etm_xport_irb_tail != etm_xport_irb_head) {
(int)(etm_xport_irb_tail - etm_xport_irb_head));
}
etm_xport_irb_mtu_sz = 0;
return (0);
} /* etm_xport_fini() */
/*
*/
/*
* etm_xport_read - try to read N bytes from the connection
* into the given buffer,
* return how many bytes actually read
* or -errno value
*/
{
} /* etm_xport_read() */
/*
* etm_xport_write - try to write N bytes to the connection
* from the given buffer,
* return how many bytes actually written
* or -errno value
*/
{
int n; /* byte cnt */
return (-EINVAL);
}
if ((n = etm_xport_valid_conn(_conn)) < 0) {
return (n);
}
/* write to the connection device's open file descriptor */
/* errno assumed set by above call */
n = (-errno);
}
return (n);
} /* etm_xport_write() */
/*
* ------------------------ miscellaneous functions --------------------------
*/
/*
* etm_xport_get_opt - get a connection's transport option value,
* return the current value
* or -errno value (ex: -ENOTSUP)
*/
{
ssize_t n; /* gen use */
rv = 0;
return (-EINVAL);
}
if ((n = etm_xport_valid_conn(_conn)) < 0) {
return (n);
}
if (etm_xport_should_fake_dd) {
&op_ctl);
} else if (use_vldc) {
if (opt == ETM_XPORT_OPT_MTU_SZ) {
} else {
return (-EINVAL);
}
} else {
}
if (n < 0) {
/* errno assumed set by above call */
} else {
}
return (rv);
} /* etm_xport_get_opt() */