OpenSolaris_b135/lib/scsi/libscsi/common/scsi_engine.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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"

#include <sys/types.h>
#include <sys/isa_defs.h>
#include <sys/systeminfo.h>
#include <sys/scsi/generic/commands.h>
#include <sys/scsi/impl/commands.h>
#include <sys/scsi/impl/uscsi.h>

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <dlfcn.h>
#include <limits.h>

#include <scsi/libscsi.h>
#include "libscsi_impl.h"

static const libscsi_engine_t *
get_engine(libscsi_hdl_t *hp, const char *name)
{
	libscsi_engine_impl_t *eip;
	const libscsi_engine_t *ep;
	const char *engine_path, *p, *q;
	char engine_dir[MAXPATHLEN];
	char engine_lib[MAXPATHLEN];
	char init_name[MAXPATHLEN];
	void *dl_hdl;
	libscsi_engine_init_f init;
	boolean_t found_lib = B_FALSE, found_init = B_FALSE;
	int dirs_tried = 0;
	char isa[257];

	for (eip = hp->lsh_engines; eip != NULL; eip = eip->lsei_next) {
		if (strcmp(eip->lsei_engine->lse_name, name) == 0)
			return (eip->lsei_engine);
	}

	if ((engine_path = getenv("LIBSCSI_ENGINE_PATH")) == NULL)
		engine_path = LIBSCSI_DEFAULT_ENGINE_PATH;

#if defined(_LP64)
	if (sysinfo(SI_ARCHITECTURE_64, isa, sizeof (isa)) < 0)
		isa[0] = '\0';
#else
	isa[0] = '\0';
#endif

	for (p = engine_path, q = strchr(p, ':'); p != NULL; p = q) {
		if (q != NULL) {
			ptrdiff_t len = q - p;
			(void) strncpy(engine_dir, p, len);
			engine_dir[len] = '\0';
			while (*q == ':')
				++q;
			if (*q == '\0')
				q = NULL;
			if (len == 0)
				continue;
		} else {
			(void) strcpy(engine_dir, p);
		}
		if (engine_dir[0] != '/')
			continue;

		++dirs_tried;

		(void) snprintf(engine_lib, MAXPATHLEN, "%s/%s/%s%s",
		    engine_dir, isa, name, LIBSCSI_ENGINE_EXT);

		dl_hdl = dlopen(engine_lib,
		    RTLD_LOCAL | RTLD_LAZY | RTLD_PARENT);
		if (dl_hdl == NULL) {
			if (!found_lib)
				(void) libscsi_error(hp, ESCSI_NOENGINE,
				    "unable to dlopen %s: %s", engine_lib,
				    dlerror());
			continue;
		}
		found_lib = B_TRUE;
		(void) snprintf(init_name, MAXPATHLEN, "libscsi_%s_init", name);
		init = (libscsi_engine_init_f)dlsym(dl_hdl, init_name);
		if (init == NULL) {
			if (!found_init)
				(void) libscsi_error(hp, ESCSI_NOENGINE,
				    "failed to find %s in %s: %s", init_name,
				    engine_lib, dlerror());
			(void) dlclose(dl_hdl);
			continue;
		}
		if ((ep = init(hp)) == NULL) {
			(void) dlclose(dl_hdl);
			/*
			 * libscsi errno set by init.
			 */
			return (NULL);
		}
		if (ep->lse_libversion != hp->lsh_version) {
			(void) dlclose(dl_hdl);
			(void) libscsi_error(hp, ESCSI_ENGINE_VER, "engine "
			    "%s version %u does not match library version %u",
			    engine_lib, ep->lse_libversion, hp->lsh_version);
			return (NULL);
		}

		eip = libscsi_zalloc(hp, sizeof (libscsi_engine_impl_t));
		if (eip == NULL) {
			(void) dlclose(dl_hdl);
			return (NULL);
		}
		eip->lsei_engine = ep;
		eip->lsei_dl_hdl = dl_hdl;
		eip->lsei_next = hp->lsh_engines;
		hp->lsh_engines = eip;

		return (ep);
	}

	if (dirs_tried == 0)
		(void) libscsi_error(hp, ESCSI_ENGINE_BADPATH, "no valid "
		    "directories found in engine path %s", engine_path);

	return (NULL);
}

static void
scsi_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;
	}
}

libscsi_target_t *
libscsi_open(libscsi_hdl_t *hp, const char *engine, const void *target)
{
	const libscsi_engine_t *ep;
	libscsi_target_t *tp;
	void *private;

	if (engine == NULL) {
		if ((engine = getenv("LIBSCSI_DEFAULT_ENGINE")) == NULL)
			engine = LIBSCSI_DEFAULT_ENGINE;
	}

	if ((ep = get_engine(hp, engine)) == NULL)
		return (NULL);

	if ((tp = libscsi_zalloc(hp, sizeof (libscsi_target_t))) == NULL)
		return (NULL);

	if ((private = ep->lse_ops->lseo_open(hp, target)) == NULL) {
		libscsi_free(hp, tp);
		return (NULL);
	}

	scsi_parse_mtbf("LIBSCSI_MTBF_CDB", &tp->lst_mtbf_cdb);
	scsi_parse_mtbf("LIBSCSI_MTBF_READ", &tp->lst_mtbf_read);
	scsi_parse_mtbf("LIBSCSI_MTBF_WRITE", &tp->lst_mtbf_write);

	tp->lst_hdl = hp;
	tp->lst_engine = ep;
	tp->lst_priv = private;

	++hp->lsh_targets;

	if (libscsi_get_inquiry(hp, tp) != 0) {
		libscsi_close(hp, tp);
		return (NULL);
	}

	return (tp);
}

libscsi_hdl_t *
libscsi_get_handle(libscsi_target_t *tp)
{
	return (tp->lst_hdl);
}

void
libscsi_close(libscsi_hdl_t *hp, libscsi_target_t *tp)
{
	tp->lst_engine->lse_ops->lseo_close(hp, tp->lst_priv);
	libscsi_free(hp, tp->lst_vendor);
	libscsi_free(hp, tp->lst_product);
	libscsi_free(hp, tp->lst_revision);
	libscsi_free(hp, tp);
	--hp->lsh_targets;
}

sam4_status_t
libscsi_action_get_status(const libscsi_action_t *ap)
{
	const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;

	return (aip->lsai_status);
}

/*
 * Set the timeout in seconds for this action.  If no timeout is specified
 * or if the timeout is set to 0, an implementation-specific timeout will be
 * used (which may vary based on the target, command or other variables).
 * Not all engines support all timeout values.  Setting the timeout to a value
 * not supported by the engine will cause engine-defined behavior when the
 * action is executed.
 */
void
libscsi_action_set_timeout(libscsi_action_t *ap, uint32_t timeout)
{
	libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;

	aip->lsai_timeout = timeout;
}

/*
 * Obtain the timeout setting for this action.
 */
uint32_t
libscsi_action_get_timeout(const libscsi_action_t *ap)
{
	const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;

	return (aip->lsai_timeout);
}

/*
 * Returns the flags associated with this action.  Never fails.
 */
uint_t
libscsi_action_get_flags(const libscsi_action_t *ap)
{
	const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;

	return (aip->lsai_flags);
}

/*
 * Returns the address of the action's CDB.  The CDB buffer is guaranteed to
 * be large enough to hold the complete CDB for the command specified when the
 * action was allocated.  Therefore, changing the command/opcode portion of
 * the CDB has undefined effects.  The remainder of the CDB may be modified.
 */
uint8_t *
libscsi_action_get_cdb(const libscsi_action_t *ap)
{
	const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;

	return (aip->lsai_cdb);
}

/*
 * Places the address of the action buffer in the location pointed to by bp,
 * if bp is not NULL.  If ap is not NULL, it will contain the allocated size
 * of the buffer itself.  If vp is not NULL, it will contain the number of
 * bytes of valid data currently stored in the buffer.
 *
 * If the action has LIBSCSI_AF_WRITE set and it has not yet been executed
 * successfully, the entire buffer is assumed to contain valid data.
 *
 * If the action has LIBSCSI_AF_READ set and it has not yet been executed
 * successfully, the amount of valid data is 0.
 *
 * If both LIBSCSI_AF_READ and LIBSCSI_AF_WRITE are clear, this function
 * fails with ESCSI_BADFLAGS to indicate that the action flags are
 * incompatible with the action data buffer.
 */
int
libscsi_action_get_buffer(const libscsi_action_t *ap, uint8_t **bp,
    size_t *sp, size_t *vp)
{
	const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;

	if ((aip->lsai_flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE)) == 0)
		return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS,
		    "data buffer not supported for actions with both "
		    "LIBSCSI_AF_READ and LIBSCSI_AF_WRITE clear"));

	if ((aip->lsai_flags & LIBSCSI_AF_WRITE) &&
	    aip->lsai_status == LIBSCSI_STATUS_INVALID) {
		if (bp != NULL)
			*bp = aip->lsai_data;
		if (sp != NULL)
			*sp = aip->lsai_data_alloc;
		if (vp != NULL)
			*vp = aip->lsai_data_alloc;

		return (0);
	}

	if ((aip->lsai_flags & LIBSCSI_AF_READ) &&
	    aip->lsai_status != LIBSCSI_STATUS_INVALID) {
		if (bp != NULL)
			*bp = aip->lsai_data;
		if (sp != NULL)
			*sp = aip->lsai_data_alloc;
		if (vp != NULL)
			*vp = aip->lsai_data_len;

		return (0);
	}

	if (aip->lsai_flags & LIBSCSI_AF_WRITE) {
		if (bp != NULL)
			*bp = NULL;
		if (sp != NULL)
			*sp = NULL;
		if (vp != NULL)
			*vp = 0;
	} else {
		if (bp != NULL)
			*bp = aip->lsai_data;
		if (sp != NULL)
			*sp = aip->lsai_data_alloc;
		if (vp != NULL)
			*vp = 0;
	}

	return (0);
}

/*
 * Obtain a pointer to the sense buffer for this action, if any, along with
 * the size of the sense buffer and the amount of valid data it contains.
 */
int
libscsi_action_get_sense(const libscsi_action_t *ap, uint8_t **bp,
    size_t *sp, size_t *vp)
{
	const libscsi_action_impl_t *aip = (const libscsi_action_impl_t *)ap;

	if (!(aip->lsai_flags & LIBSCSI_AF_RQSENSE))
		return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS,
		    "sense data unavailable: LIBSCSI_AF_RQSENSE is clear"));

	if (vp != NULL) {
		if (aip->lsai_status == LIBSCSI_STATUS_INVALID)
			*vp = 0;
		else
			*vp = aip->lsai_sense_len;
	}

	if (bp != NULL) {
		ASSERT(aip->lsai_sense_data != NULL);
		*bp = aip->lsai_sense_data;
	}

	if (sp != NULL)
		*sp = UINT8_MAX;

	return (0);
}

/*
 * Set the SCSI status of the action.
 *
 * Engines only.
 */
void
libscsi_action_set_status(libscsi_action_t *ap, sam4_status_t status)
{
	libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;

	ASSERT(aip->lsai_status == LIBSCSI_STATUS_INVALID);

	aip->lsai_status = status;
}

/*
 * Set the length of valid data returned by a READ action.  If the action is
 * not a READ action, or the length exceeds the size of the buffer, an error
 * results.
 *
 * Engines only.
 */
int
libscsi_action_set_datalen(libscsi_action_t *ap, size_t len)
{
	libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;

	if ((aip->lsai_flags & LIBSCSI_AF_READ) == 0)
		return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS,
		    "data cannot be returned for actions with LIBSCSI_AF_READ "
		    "clear"));
	if (len > aip->lsai_data_alloc)
		return (libscsi_error(aip->lsai_hdl, ESCSI_BADLENGTH,
		    "data length %lu exceeds allocated buffer capacity %lu",
		    (ulong_t)len, (ulong_t)aip->lsai_data_alloc));

	ASSERT(aip->lsai_data_len == 0);
	aip->lsai_data_len = len;

	return (0);
}

/*
 * Set the length of the valid sense data returned following the command, if
 * LIBSCSI_AF_RQSENSE is set for this action.  Otherwise, fail.
 *
 * Engines only.
 */
int
libscsi_action_set_senselen(libscsi_action_t *ap, size_t len)
{
	libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;

	if (!(aip->lsai_flags & LIBSCSI_AF_RQSENSE))
		return (libscsi_error(aip->lsai_hdl, ESCSI_BADFLAGS,
		    "sense data not supported: LIBSCSI_AF_RQSENSE is clear"));

	if (len > UINT8_MAX)
		return (libscsi_error(aip->lsai_hdl, ESCSI_BADLENGTH,
		    "sense length %lu exceeds allocated buffer capacity %lu",
		    (ulong_t)len, (ulong_t)UINT8_MAX));

	ASSERT(aip->lsai_sense_len == 0);
	aip->lsai_sense_len = len;

	return (0);
}

/*
 * Allocate an action object.  The object will contain a CDB area sufficiently
 * large to hold a CDB for the given command, and the CDB's opcode will be
 * filled in.  A pointer to this CDB, the contents of which may be modified by
 * the caller, may be obtained by a subsequent call to libscsi_action_cdb().
 *
 * If flags includes LIBSCSI_AF_READ or LIBSCSI_AF_WRITE, buflen must be
 * greater than zero.  Otherwise, buflen must be 0 and buf must be NULL.
 * If buflen is nonzero but buf is NULL, a suitably-sized buffer will be
 * allocated; otherwise, the specified buffer will be used.  In either case,
 * a pointer to the buffer may be obtained via a subsequent call to
 * libscsi_action_buffer().
 *
 * If flags includes LIBSCSI_AF_RQSENSE, a REQUEST SENSE command will be
 * issued immediately following the termination of the specified command.
 * A buffer will be allocated to receive this sense data.  Following successful
 * execution of the action, a pointer to this buffer and the length of
 * valid sense data may be obtained by a call to libscsi_action_sense().
 * If cmd is SPC3_CMD_REQUEST_SENSE, this flag must be clear.
 */
libscsi_action_t *
libscsi_action_alloc(libscsi_hdl_t *hp, spc3_cmd_t cmd, uint_t flags,
    void *buf, size_t buflen)
{
	libscsi_action_impl_t *aip;
	size_t cdbsz, sz;
	ptrdiff_t off;

	/*
	 * If there's no buffer, it makes no sense to try to read or write
	 * data.  Likewise, if we're neither reading nor writing data, we
	 * should not have a buffer.  Both of these are programmer error.
	 */
	if (buflen == 0 && (flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE))) {
		(void) libscsi_error(hp, ESCSI_NEEDBUF, "a buffer is "
		    "required when reading or writing");
		return (NULL);
	}
	if (buflen > 0 && !(flags & (LIBSCSI_AF_READ | LIBSCSI_AF_WRITE))) {
		(void) libscsi_error(hp, ESCSI_BADFLAGS, "one of "
		    "LIBSCSI_AF_READ and LIBSCSI_AF_WRITE must be specified "
		    "in order to use a buffer");
		return (NULL);
	}
	if (cmd == SPC3_CMD_REQUEST_SENSE && (flags & LIBSCSI_AF_RQSENSE)) {
		(void) libscsi_error(hp, ESCSI_BADFLAGS, "request sense "
		    "flag not allowed for request sense command");
		return (NULL);
	}

	if ((sz = cdbsz = libscsi_cmd_cdblen(hp, cmd)) == 0)
		return (NULL);

	/*
	 * If the caller has asked for a buffer but has not provided one, we
	 * will allocate it in our internal buffer along with the CDB and
	 * request sense space (if requested).
	 */
	if (buf == NULL)
		sz += buflen;

	if (flags & LIBSCSI_AF_RQSENSE)
		sz += UINT8_MAX;

	sz += offsetof(libscsi_action_impl_t, lsai_buf[0]);

	if ((aip = libscsi_zalloc(hp, sz)) == NULL)
		return (NULL);

	aip->lsai_hdl = hp;
	aip->lsai_flags = flags;

	off = 0;

	aip->lsai_cdb = aip->lsai_buf + off;
	aip->lsai_cdb_len = cdbsz;
	off += cdbsz;
	aip->lsai_cdb[0] = (uint8_t)cmd;

	if (buflen > 0) {
		if (buf != NULL) {
			aip->lsai_data = buf;
		} else {
			aip->lsai_data = aip->lsai_buf + off;
			off += buflen;
		}
		aip->lsai_data_alloc = buflen;
		if (flags & LIBSCSI_AF_WRITE)
			aip->lsai_data_len = buflen;
	}

	if (flags & LIBSCSI_AF_RQSENSE) {
		aip->lsai_sense_data = aip->lsai_buf + off;
		off += UINT8_MAX;
	}

	aip->lsai_status = LIBSCSI_STATUS_INVALID;

	return ((libscsi_action_t *)aip);
}

void
libscsi_action_free(libscsi_action_t *ap)
{
	libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;

	libscsi_free(aip->lsai_hdl, aip);
}

/*
 * For testing purposes, we allow data to be corrupted via an environment
 * variable setting.  This helps ensure that higher level software can cope with
 * arbitrarily broken targets.  The mtbf value represents the number of bytes we
 * will see, on average, in between each failure.  Therefore, for each N bytes,
 * we would expect to see (N / mtbf) bytes of corruption.
 */
static void
scsi_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
libscsi_exec(libscsi_action_t *ap, libscsi_target_t *tp)
{
	libscsi_action_impl_t *aip = (libscsi_action_impl_t *)ap;
	libscsi_hdl_t *hp = aip->lsai_hdl;
	int ret;

	if (tp->lst_mtbf_write != 0 &&
	    (aip->lsai_flags & LIBSCSI_AF_WRITE)) {
		scsi_inject_errors(aip->lsai_data, aip->lsai_data_len,
		    tp->lst_mtbf_write);
	}

	if (tp->lst_mtbf_cdb != 0) {
		scsi_inject_errors(aip->lsai_cdb, aip->lsai_cdb_len,
		    tp->lst_mtbf_cdb);
	}

	ret = tp->lst_engine->lse_ops->lseo_exec(hp, tp->lst_priv, ap);

	if (ret == 0 && tp->lst_mtbf_read != 0 &&
	    (aip->lsai_flags & LIBSCSI_AF_READ)) {
		scsi_inject_errors(aip->lsai_data, aip->lsai_data_len,
		    tp->lst_mtbf_read);
	}

	return (ret);
}