#include <sys/types.h>
#include <sys/isa_defs.h>
#include <sys/systeminfo.h>
#include <sys/scsi/generic/smp_frames.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <strings.h>
#include <dlfcn.h>
#include <limits.h>
#include <pthread.h>
#include <synch.h>
#include <scsi/libsmp.h>
#include "smp_impl.h"
static pthread_mutex_t _libsmp_lock = PTHREAD_MUTEX_INITIALIZER;
static smp_engine_t *_libsmp_engines;
static int _libsmp_refcnt;
static boolean_t _libsmp_engine_dlclose;
static void
smp_engine_free(smp_engine_t *ep)
{
if (ep == NULL)
return;
smp_free(ep->se_name);
smp_free(ep);
}
static void
smp_engine_destroy(smp_engine_t *ep)
{
smp_engine_t **pp;
ASSERT(MUTEX_HELD(&_libsmp_lock));
if (ep->se_fini != NULL)
ep->se_fini(ep);
if (_libsmp_engine_dlclose)
(void) dlclose(ep->se_object);
ASSERT(ep->se_refcnt == 0);
for (pp = &_libsmp_engines; *pp != NULL; pp = &((*pp)->se_next))
if (*pp == ep)
break;
if (*pp != NULL)
*pp = (*pp)->se_next;
smp_engine_free(ep);
}
void
smp_engine_init(void)
{
(void) pthread_mutex_lock(&_libsmp_lock);
++_libsmp_refcnt;
(void) pthread_mutex_unlock(&_libsmp_lock);
}
void
smp_engine_fini(void)
{
smp_engine_t *ep;
(void) pthread_mutex_lock(&_libsmp_lock);
ASSERT(_libsmp_refcnt > 0);
if (--_libsmp_refcnt == 0) {
while (_libsmp_engines != NULL) {
ep = _libsmp_engines;
_libsmp_engines = ep->se_next;
smp_engine_destroy(ep);
}
}
(void) pthread_mutex_unlock(&_libsmp_lock);
}
static int
smp_engine_loadone(const char *path)
{
smp_engine_t *ep;
void *obj;
ASSERT(MUTEX_HELD(&_libsmp_lock));
if ((obj = dlopen(path, RTLD_PARENT | RTLD_LOCAL | RTLD_LAZY)) == NULL)
return (smp_set_errno(ESMP_NOENGINE));
if ((ep = smp_zalloc(sizeof (smp_engine_t))) == NULL) {
(void) dlclose(obj);
return (-1);
}
ep->se_object = obj;
ep->se_init = (int (*)())dlsym(obj, "_smp_init");
ep->se_fini = (void (*)())dlsym(obj, "_smp_fini");
if (ep->se_init == NULL) {
smp_engine_free(ep);
return (smp_set_errno(ESMP_BADENGINE));
}
if (ep->se_init(ep) != 0) {
smp_engine_free(ep);
return (-1);
}
return (0);
}
int
smp_engine_register(smp_engine_t *ep, int version,
const smp_engine_config_t *ecp)
{
ASSERT(MUTEX_HELD(&_libsmp_lock));
if (version != LIBSMP_ENGINE_VERSION)
return (smp_set_errno(ESMP_VERSION));
ep->se_ops = ecp->sec_ops;
ep->se_name = smp_strdup(ecp->sec_name);
if (ep->se_name == NULL)
return (-1);
ep->se_next = _libsmp_engines;
_libsmp_engines = ep;
return (0);
}
static smp_engine_t *
smp_engine_hold_cached(const char *name)
{
smp_engine_t *ep;
ASSERT(MUTEX_HELD(&_libsmp_lock));
for (ep = _libsmp_engines; ep != NULL; ep = ep->se_next) {
if (strcmp(ep->se_name, name) == 0) {
++ep->se_refcnt;
return (ep);
}
}
(void) smp_set_errno(ESMP_NOENGINE);
return (NULL);
}
static smp_engine_t *
smp_engine_hold(const char *name)
{
smp_engine_t *ep;
const char *pluginpath, *p, *q;
char pluginroot[PATH_MAX];
char path[PATH_MAX];
char isa[257];
(void) pthread_mutex_lock(&_libsmp_lock);
ep = smp_engine_hold_cached(name);
if (ep != NULL) {
(void) pthread_mutex_unlock(&_libsmp_lock);
return (ep);
}
#if defined(_LP64)
if (sysinfo(SI_ARCHITECTURE_64, isa, sizeof (isa)) < 0)
isa[0] = '\0';
#else
isa[0] = '\0';
#endif
if ((pluginpath = getenv("SMP_PLUGINPATH")) == NULL)
pluginpath = LIBSMP_DEFAULT_PLUGINDIR;
_libsmp_engine_dlclose = (getenv("SMP_NODLCLOSE") == NULL);
for (p = pluginpath; p != NULL; p = q) {
if ((q = strchr(p, ':')) != NULL) {
ptrdiff_t len = q - p;
(void) strncpy(pluginroot, p, len);
pluginroot[len] = '\0';
while (*q == ':')
++q;
if (*q == '\0')
q = NULL;
if (len == 0)
continue;
} else {
(void) strcpy(pluginroot, p);
}
if (pluginroot[0] != '/')
continue;
(void) snprintf(path, PATH_MAX, "%s/%s/%s/%s%s",
pluginroot, LIBSMP_PLUGIN_ENGINE,
isa, name, LIBSMP_PLUGIN_EXT);
if (smp_engine_loadone(path) == 0) {
ep = smp_engine_hold_cached(name);
(void) pthread_mutex_unlock(&_libsmp_lock);
return (ep);
}
}
return (NULL);
}
static void
smp_engine_rele(smp_engine_t *ep)
{
(void) pthread_mutex_lock(&_libsmp_lock);
ASSERT(ep->se_refcnt > 0);
--ep->se_refcnt;
(void) pthread_mutex_unlock(&_libsmp_lock);
}
static void
smp_parse_mtbf(const char *envvar, uint_t *intp)
{
const char *strval;
int intval;
if ((strval = getenv(envvar)) != NULL &&
(intval = atoi(strval)) > 0) {
srand48(gethrtime());
*intp = intval;
}
}
smp_target_t *
smp_open(const smp_target_def_t *tdp)
{
smp_engine_t *ep;
smp_target_t *tp;
void *private;
const char *engine;
if ((engine = tdp->std_engine) == NULL) {
if ((engine = getenv("LIBSMP_DEFAULT_ENGINE")) == NULL)
engine = LIBSMP_DEFAULT_ENGINE;
}
if ((ep = smp_engine_hold(engine)) == NULL)
return (NULL);
if ((tp = smp_zalloc(sizeof (smp_target_t))) == NULL) {
smp_engine_rele(ep);
return (NULL);
}
if ((private = ep->se_ops->seo_open(tdp->std_def)) == NULL) {
smp_engine_rele(ep);
smp_free(tp);
return (NULL);
}
smp_parse_mtbf("LIBSMP_MTBF_REQUEST", &tp->st_mtbf_request);
smp_parse_mtbf("LIBSMP_MTBF_RESPONSE", &tp->st_mtbf_response);
tp->st_engine = ep;
tp->st_priv = private;
if (smp_plugin_load(tp) != 0) {
smp_close(tp);
return (NULL);
}
return (tp);
}
void
smp_target_name(const smp_target_t *tp, char *buf, size_t len)
{
tp->st_engine->se_ops->seo_target_name(tp->st_priv, buf, len);
}
uint64_t
smp_target_addr(const smp_target_t *tp)
{
return (tp->st_engine->se_ops->seo_target_addr(tp->st_priv));
}
const char *
smp_target_vendor(const smp_target_t *tp)
{
return (tp->st_vendor);
}
const char *
smp_target_product(const smp_target_t *tp)
{
return (tp->st_product);
}
const char *
smp_target_revision(const smp_target_t *tp)
{
return (tp->st_revision);
}
const char *
smp_target_component_vendor(const smp_target_t *tp)
{
return (tp->st_component_vendor);
}
uint16_t
smp_target_component_id(const smp_target_t *tp)
{
return (tp->st_component_id);
}
uint8_t
smp_target_component_revision(const smp_target_t *tp)
{
return (tp->st_component_revision);
}
uint_t
smp_target_getcap(const smp_target_t *tp)
{
uint_t cap = 0;
if (tp->st_repgen.srgr_long_response)
cap |= SMP_TARGET_C_LONG_RESP;
if (tp->st_repgen.srgr_zoning_supported)
cap |= SMP_TARGET_C_ZONING;
if (tp->st_repgen.srgr_number_of_zone_grps == SMP_ZONE_GROUPS_256)
cap |= SMP_TARGET_C_ZG_256;
return (cap);
}
void
smp_target_set_change_count(smp_target_t *tp, uint16_t cc)
{
tp->st_change_count = cc;
}
uint16_t
smp_target_get_change_count(const smp_target_t *tp)
{
return (tp->st_change_count);
}
uint8_t
smp_target_get_number_of_phys(const smp_target_t *tp)
{
return (tp->st_repgen.srgr_number_of_phys);
}
uint16_t
smp_target_get_exp_route_indexes(const smp_target_t *tp)
{
return (tp->st_repgen.srgr_exp_route_indexes);
}
void
smp_close(smp_target_t *tp)
{
smp_free(tp->st_vendor);
smp_free(tp->st_product);
smp_free(tp->st_revision);
smp_free(tp->st_component_vendor);
smp_plugin_unload(tp);
tp->st_engine->se_ops->seo_close(tp->st_priv);
smp_engine_rele(tp->st_engine);
smp_free(tp);
}
void
smp_action_set_timeout(smp_action_t *ap, uint32_t timeout)
{
ap->sa_timeout = timeout;
}
uint32_t
smp_action_get_timeout(const smp_action_t *ap)
{
return (ap->sa_timeout);
}
const smp_function_def_t *
smp_action_get_function_def(const smp_action_t *ap)
{
return (ap->sa_def);
}
size_t
smp_action_get_rqsd(const smp_action_t *ap)
{
return (ap->sa_request_rqsd);
}
void
smp_action_get_request(const smp_action_t *ap, void **reqp, size_t *dlenp)
{
if (reqp != NULL) {
if (ap->sa_request_data_off >= 0) {
*reqp = ap->sa_request + ap->sa_request_data_off;
} else {
*reqp = NULL;
}
}
if (dlenp != NULL)
*dlenp = ap->sa_request_alloc_len -
(ap->sa_request_data_off + sizeof (smp_crc_t));
}
void
smp_action_get_response(const smp_action_t *ap, smp_result_t *resultp,
void **respp, size_t *dlenp)
{
if (resultp != NULL)
*resultp = ap->sa_result;
if (respp != NULL)
*respp = (ap->sa_response_data_len > 0) ?
(ap->sa_response + ap->sa_response_data_off) : NULL;
if (dlenp != NULL)
*dlenp = ap->sa_response_data_len;
}
void
smp_action_get_request_frame(const smp_action_t *ap, void **reqp, size_t *alenp)
{
if (reqp != NULL)
*reqp = ap->sa_request;
if (alenp != NULL)
*alenp = ap->sa_request_alloc_len;
}
void
smp_action_get_response_frame(const smp_action_t *ap,
void **respp, size_t *lenp)
{
if (respp != NULL)
*respp = ap->sa_response;
if (lenp != NULL) {
if (ap->sa_flags & SMP_ACTION_F_EXEC)
*lenp = ap->sa_response_engine_len;
else
*lenp = ap->sa_response_alloc_len;
}
}
void
smp_action_set_response_len(smp_action_t *ap, size_t elen)
{
ap->sa_response_engine_len = elen;
}
void
smp_action_set_result(smp_action_t *ap, smp_result_t result)
{
ap->sa_result = result;
}
smp_action_t *
smp_action_xalloc(smp_function_t fn, smp_target_t *tp,
void *rq, size_t rqsd, void *rs, size_t rslen)
{
smp_plugin_t *pp;
const smp_function_def_t *dp = NULL;
smp_action_t *ap;
uint_t cap;
size_t rqlen, len;
uint8_t *alloc;
int i;
cap = smp_target_getcap(tp);
for (pp = tp->st_plugin_first; pp != NULL; pp = pp->sp_next) {
if (pp->sp_functions == NULL)
continue;
for (i = 0; pp->sp_functions[i].sfd_rq_len != NULL; i++) {
dp = &pp->sp_functions[i];
if (dp->sfd_function == fn &&
((cap & dp->sfd_capmask) == dp->sfd_capset))
break;
}
}
if (dp == NULL) {
(void) smp_set_errno(ESMP_BADFUNC);
return (NULL);
}
if (rq == NULL) {
if ((rqlen = dp->sfd_rq_len(rqsd, tp)) == 0)
return (NULL);
} else if (rqlen < SMP_REQ_MINLEN) {
(void) smp_set_errno(ESMP_RANGE);
return (NULL);
}
if (rs == NULL) {
rslen = 1020 + SMP_RESP_MINLEN;
} else if (rslen < SMP_RESP_MINLEN) {
(void) smp_set_errno(ESMP_RANGE);
return (NULL);
}
len = offsetof(smp_action_t, sa_buf[0]);
if (rq == NULL)
len += rqlen;
if (rs == NULL)
len += rslen;
if ((ap = smp_zalloc(len)) == NULL)
return (NULL);
ap->sa_def = dp;
alloc = ap->sa_buf;
if (rq == NULL) {
ap->sa_request = alloc;
alloc += rqlen;
}
ap->sa_request_alloc_len = rqlen;
if (rs == NULL) {
ap->sa_response = alloc;
alloc += rslen;
}
ap->sa_response_alloc_len = rslen;
ASSERT(alloc - (uint8_t *)ap == len);
ap->sa_request_data_off = dp->sfd_rq_dataoff(ap, tp);
ap->sa_flags |= SMP_ACTION_F_OFFSET;
return (ap);
}
smp_action_t *
smp_action_alloc(smp_function_t fn, smp_target_t *tp, size_t rqsd)
{
return (smp_action_xalloc(fn, tp, NULL, rqsd, NULL, 0));
}
void
smp_action_free(smp_action_t *ap)
{
if (ap == NULL)
return;
smp_free(ap);
}
static void
smp_inject_errors(void *data, size_t len, uint_t mtbf)
{
char *buf = data;
double prob;
size_t index;
if (len == 0)
return;
prob = (double)len / mtbf;
while (prob > 1) {
index = lrand48() % len;
buf[index] = (lrand48() % 256);
prob -= 1;
}
if (drand48() <= prob) {
index = lrand48() % len;
buf[index] = (lrand48() % 256);
}
}
int
smp_exec(smp_action_t *ap, smp_target_t *tp)
{
const smp_function_def_t *dp;
int ret;
dp = ap->sa_def;
dp->sfd_rq_setframe(ap, tp);
if (tp->st_mtbf_request != 0) {
smp_inject_errors(ap->sa_request, ap->sa_request_alloc_len,
tp->st_mtbf_request);
}
ret = tp->st_engine->se_ops->seo_exec(tp->st_priv, ap);
if (ret == 0 && tp->st_mtbf_response != 0) {
smp_inject_errors(ap->sa_response, ap->sa_response_engine_len,
tp->st_mtbf_response);
}
if (ret != 0)
return (ret);
ap->sa_flags |= SMP_ACTION_F_EXEC;
ap->sa_response_data_len = dp->sfd_rs_datalen(ap, tp);
ap->sa_response_data_off = dp->sfd_rs_dataoff(ap, tp);
dp->sfd_rs_getparams(ap, tp);
ap->sa_flags |= SMP_ACTION_F_DECODE;
return (0);
}