OpenSolaris_b135/common/devid/devid_scsi.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * These functions are used to encode SCSI INQUIRY data into
 * Solaris devid / guid values.
 */

#ifndef _KERNEL
#include <stdio.h>
#endif /* _KERNEL */

#include <sys/inttypes.h>
#include <sys/types.h>
#include <sys/stropts.h>
#include <sys/debug.h>
#include <sys/isa_defs.h>
#include <sys/dditypes.h>
#include <sys/ddi_impldefs.h>
#include <sys/scsi/scsi.h>
#ifndef _KERNEL
#include <sys/libdevid.h>
#endif /* !_KERNEL */
#include "devid_impl.h"

#define	SCSI_INQUIRY_VID_POS			9
#define	SCSI_INQUIRY_VID_SUN			"SUN"
#define	SCSI_INQUIRY_VID_SUN_LEN		3
#define	SCSI_INQUIRY_VID_HITACHI		"HITACHI"
#define	SCSI_INQUIRY_VID_HITACHI_LEN		7
#define	SCSI_INQUIRY_PID_HITACHI_OPEN		"OPEN-"
#define	SCSI_INQUIRY_PID_HITACHI_OPEN_LEN	5
#define	SCSI_INQUIRY_VID_EMC			"EMC     "
#define	SCSI_INQUIRY_VID_EMC_LEN		8
#define	SCSI_INQUIRY_PID_EMC_SYMMETRIX		"SYMMETRIX       "
#define	SCSI_INQUIRY_PID_EMC_SYMMETRIX_LEN	16

#define	MSG_NOT_STANDARDS_COMPLIANT "!Page83 data not standards compliant "
#define	MSG_NOT_STANDARDS_COMPLIANT_SIZE	( \
	sizeof (MSG_NOT_STANDARDS_COMPLIANT) + \
	sizeof (((struct scsi_inquiry *)NULL)->inq_vid) + \
	sizeof (((struct scsi_inquiry *)NULL)->inq_pid) + \
	sizeof (((struct scsi_inquiry *)NULL)->inq_revision) + 4)

#define	IS_DEVID_GUID_TYPE(type) ((type == DEVID_SCSI3_WWN)	|| \
				(IS_DEVID_SCSI3_VPD_TYPE(type)))

#define	IS_DEVID_SCSI_TYPE(type) ((IS_DEVID_GUID_TYPE(type)) || \
				(type == DEVID_SCSI_SERIAL))

/*
 * The max inquiry page 83 size as expected in the code today
 * is 0xf0 bytes. Defining a constant to make it easy incase
 * this needs to be changed at a later time.
 */

#define	SCMD_MAX_INQUIRY_PAGE83_SIZE			0xFF
#define	SCMD_MIN_INQUIRY_PAGE83_SIZE			0x08
#define	SCMD_INQUIRY_PAGE83_HDR_SIZE			4
#define	SCSI_INQUIRY_PAGE83_EMC_SYMMETRIX_ID_LEN	16

#define	SCMD_MAX_INQUIRY_PAGE80_SIZE	0xFF
#define	SCMD_MIN_INQUIRY_PAGE80_SIZE	0x04

#define	SCMD_MIN_STANDARD_INQUIRY_SIZE	0x04

#define	SCMD_INQUIRY_PAGE83_IDENT_DESC_HDR_SIZE		4

#define	SCMD_INQUIRY_VPD_TYPE_T10	0x01
#define	SCMD_INQUIRY_VPD_TYPE_EUI	0x02
#define	SCMD_INQUIRY_VPD_TYPE_NAA	0x03
#define	SCMD_INQUIRY_VPD_TYPE_RTP	0x04
#define	SCMD_INQUIRY_VPD_TYPE_TPG	0x05
#define	SCMD_INQUIRY_VPD_TYPE_LUG	0x06
#define	SCMD_INQUIRY_VPD_TYPE_MD5	0x07
#define	SCMD_INQUIRY_VPD_TYPE_SSN	0x08

static int is_page83_data_valid(uchar_t *inq83, size_t inq83_len);
static int is_page80_data_valid(uchar_t *inq80, size_t inq80_len);
static int is_initialized_id(uchar_t *id, size_t id_len);

static void encode_scsi3_page83(int version, uchar_t *inq83,
    size_t inq83_len, uchar_t **id, size_t *id_len, ushort_t *id_type);
static void encode_scsi3_page83_emc(int version, uchar_t *inq83,
    size_t inq83_len, uchar_t **id, size_t *id_len, ushort_t *id_type);
static void encode_serialnum(int version, uchar_t *inq, uchar_t *inq80,
    size_t inq80_len, uchar_t **id, size_t *id_len, ushort_t *id_type);
static void encode_sun_serialnum(int version, uchar_t *inq,
    size_t inq_len, uchar_t **id, size_t *id_len, ushort_t *id_type);

static int devid_scsi_init(char *driver_name,
    uchar_t *raw_id, size_t raw_id_len, ushort_t raw_id_type,
    ddi_devid_t *ret_devid);

static char ctoi(char c);

/*
 *    Function: ddi_/devid_scsi_encode
 *
 * Description: This routine finds and encodes a unique devid
 *
 *   Arguments: version - id encode algorithm version
 *		driver_name - binding driver name (if ! known use NULL)
 *		inq - standard inquiry buffer
 *		inq_len - standard inquiry buffer length
 *		inq80 - serial number inquiry buffer
 *		inq80_len - serial number inquiry buffer length
 *		inq83 - vpd inquiry buffer
 *		inq83_len - vpd inquiry buffer length
 *		devid - id returned
 *
 * Return Code: DEVID_SUCCESS - success
 *		DEVID_FAILURE - failure
 *		DEVID_RETRY - LUN is in a transitional state.  A delay should
 *		occur and then this inquiry data should be re-acquired and
 *		this function should be called again.
 */
int
#ifdef _KERNEL
ddi_devid_scsi_encode(
#else /* ! _KERNEL */
devid_scsi_encode(
#endif /* _KERNEL */
    int version,	/* IN */
    char *driver_name,	/* IN */
    uchar_t *inq,	/* IN */
    size_t inq_len,	/* IN */
    uchar_t *inq80,	/* IN */
    size_t inq80_len,	/* IN */
    uchar_t *inq83,	/* IN */
    size_t inq83_len,	/* IN */
    ddi_devid_t *devid)	/* OUT */
{
	int			rval		= DEVID_FAILURE;
	uchar_t			*id		= NULL;
	size_t			id_len		= 0;
	ushort_t		id_type		= DEVID_NONE;
	struct scsi_inquiry	*inq_std	= (struct scsi_inquiry *)inq;
#ifdef	_KERNEL
	char			*msg		= NULL;
#endif	/* _KERNEL */

	DEVID_ASSERT(devid != NULL);

	/* verify valid version */
	if (version > DEVID_SCSI_ENCODE_VERSION_LATEST) {
		return (rval);
	}

	/* make sure minimum inquiry bytes are available */
	if (inq_len < SCMD_MIN_STANDARD_INQUIRY_SIZE) {
		return (rval);
	}

	/*
	 * If 0x83 is availible, that is the best choice.  Our next choice is
	 * 0x80.  If neither are availible, we leave it to the caller to
	 * determine possible alternate ID, although discouraged.  In the
	 * case of the target drivers they create a fabricated id which is
	 * stored in the acyl.  The HBA drivers should avoid using an
	 * alternate id.  Although has already created a hack of using the
	 * node wwn in some cases.  Which needs to be carried forward for
	 * legacy reasons.
	 */
	if (inq83 != NULL) {
		/*
		 * Perform page 83 validation tests and report offenders.
		 * We cannot enforce the page 83 specification because
		 * many Sun partners (ex. HDS) do not conform to the
		 * standards yet.
		 */
		if (is_page83_data_valid(inq83, inq83_len) ==
		    DEVID_RET_INVALID) {
			/*
			 * invalid page 83 data.  bug 4939576 introduced
			 * handling for EMC non-standard data.
			 */
			if ((bcmp(inq_std->inq_vid, SCSI_INQUIRY_VID_EMC,
			    SCSI_INQUIRY_VID_EMC_LEN) == 0) &&
			    (bcmp(inq_std->inq_pid,
			    SCSI_INQUIRY_PID_EMC_SYMMETRIX,
			    SCSI_INQUIRY_PID_EMC_SYMMETRIX_LEN) == 0)) {
				encode_scsi3_page83_emc(version, inq83,
				    inq83_len, &id, &id_len, &id_type);
			}
#ifdef	_KERNEL
			/*
			 * invalid page 83 data. Special hack for HDS
			 * specific device, to suppress the warning msg.
			 */
			if ((bcmp(inq_std->inq_vid, SCSI_INQUIRY_VID_HITACHI,
			    SCSI_INQUIRY_VID_HITACHI_LEN) != 0) ||
			    (bcmp(inq_std->inq_pid,
			    SCSI_INQUIRY_PID_HITACHI_OPEN,
			    SCSI_INQUIRY_PID_HITACHI_OPEN_LEN) != 0)) {
				/*
				 * report the page 0x83 standards violation.
				 */
				msg = kmem_alloc(
				    MSG_NOT_STANDARDS_COMPLIANT_SIZE,
				    KM_SLEEP);
				(void) strcpy(msg, MSG_NOT_STANDARDS_COMPLIANT);
				(void) strncat(msg, inq_std->inq_vid,
				    sizeof (inq_std->inq_vid));
				(void) strcat(msg, " ");
				(void) strncat(msg, inq_std->inq_pid,
				    sizeof (inq_std->inq_pid));
				(void) strcat(msg, " ");
				(void) strncat(msg, inq_std->inq_revision,
				    sizeof (inq_std->inq_revision));
				(void) strcat(msg, "\n");
				cmn_err(CE_WARN, msg);
				kmem_free(msg,
				    MSG_NOT_STANDARDS_COMPLIANT_SIZE);
			}
#endif	/* _KERNEL */
		}

		if (id_type == DEVID_NONE) {
			encode_scsi3_page83(version, inq83,
			    inq83_len, &id, &id_len, &id_type);
		}
	}

	/*
	 * If no vpd page is available at this point then we
	 * attempt to use a SCSI serial number from page 0x80.
	 */
	if ((id_type == DEVID_NONE) &&
	    (inq != NULL) &&
	    (inq80 != NULL)) {
		if (is_page80_data_valid(inq80, inq80_len) == DEVID_RET_VALID) {
			encode_serialnum(version, inq, inq80,
			    inq80_len, &id, &id_len, &id_type);
		}
	}

	/*
	 * If no vpd page  or serial is available at this point and
	 * it's a SUN disk it conforms to the disk qual. 850 specifications
	 * and we can fabricate a serial number id based on the standard
	 * inquiry page.
	 */
	if ((id_type == DEVID_NONE) &&
	    (inq != NULL)) {
		encode_sun_serialnum(version, inq, inq_len,
		    &id, &id_len, &id_type);
	}

	if (id_type != DEVID_NONE) {
		if (is_initialized_id(id, id_len) == DEVID_RET_VALID) {
			rval = devid_scsi_init(driver_name,
			    id, id_len, id_type, devid);
		} else {
			rval = DEVID_RETRY;
		}
		DEVID_FREE(id, id_len);
	}

	return (rval);
}


/*
 *    Function: is_page83_data_valid
 *
 * Description: This routine is used to validate the page 0x83 data
 *		passed in valid based on the standards specification.
 *
 *   Arguments: inq83 -
 *		inq83_len -
 *
 * Return Code: DEVID_RET_VALID
 *              DEVID_RET_INVALID
 *
 */
static int
is_page83_data_valid(uchar_t *inq83, size_t inq83_len)
{

	int 	covered_desc_len	= 0;
	int	dlen			= 0;
	uchar_t	*dblk			= NULL;

	DEVID_ASSERT(inq83 != NULL);

	/* if not large enough fail */
	if (inq83_len < SCMD_MIN_INQUIRY_PAGE83_SIZE)
		return (DEVID_RET_INVALID);

	/*
	 * Ensuring that the Peripheral device type(bits 0 - 4) has
	 * the valid settings - the value 0x1f indicates no device type.
	 * Only this value can be validated since all other fields are
	 * either used or reserved.
	 */
	if ((inq83[0] & DTYPE_MASK) == DTYPE_UNKNOWN) {
		/* failed-peripheral devtype */
		return (DEVID_RET_INVALID);
	}

	/*
	 * Ensure that the page length field - third and 4th bytes
	 * contain a non zero length value. Our implementation
	 * does not seem to expect more that 255 bytes of data...
	 * what is to be done if the reported size is > 255 bytes?
	 * Yes the device will return only 255 bytes as we provide
	 * buffer to house only that much data but the standards
	 * prevent the targets from reporting the truncated size
	 * in this field.
	 *
	 * Currently reporting sizes more than 255 as failure.
	 *
	 */

	if ((inq83[2] == 0) && (inq83[3] == 0)) {
		/* length field is 0! */
		return (DEVID_RET_INVALID);
	}
	if (inq83[3] > (SCMD_MAX_INQUIRY_PAGE83_SIZE - 3)) {
		/* length field exceeds expected size of 255 bytes */
		return (DEVID_RET_INVALID);
	}

	/*
	 * Validation of individual descriptor blocks are done in the
	 * following while loop. It is possible to have multiple
	 * descriptor blocks.
	 * the 'dblk' pointer will be pointing to the start of
	 * each entry of the descriptor block.
	 */
	covered_desc_len = 0;
	dblk = &inq83[4]; /* start of first decriptor blk */
	while (covered_desc_len < inq83[3]) {

		/*
		 * Ensure that the length field is non zero
		 * Further length validations will be done
		 * along with the 'identifier type' as some of
		 * the lengths are dependent on it.
		 */
		dlen = dblk[3];
		if (dlen == 0) {
			/* descr length is 0 */
			return (DEVID_RET_INVALID);
		}

		/*
		 * ensure that the size of the descriptor block does
		 * not claim to be larger than the entire page83
		 * data that has been received.
		 */
		if ((covered_desc_len + dlen) > inq83[3]) {
			/* failed-descr length */
			return (DEVID_RET_INVALID);
		}

		/*
		 * The spec says that if the PIV field is 0 OR the
		 * association field contains value other than 1 and 2,
		 * then the protocol identifier field should be ignored.
		 * If association field contains a value of 1 or 2
		 * and the PIV field is set, then the protocol identifier
		 * field has to be validated.
		 * The protocol identifier values 0 - f are either assigned
		 * or reserved. Nothing to validate here, hence skipping
		 * over to the next check.
		 */

		/*
		 * Check for valid code set values.
		 * All possible values are reserved or assigned. Nothing
		 * to validate - skipping over.
		 */

		/*
		 * Identifier Type validation
		 * All SPC3rev22 identified types and the expected lengths
		 * are validated.
		 */
		switch (dblk[1] & 0x0f) {
		case SCMD_INQUIRY_VPD_TYPE_T10: /* T10 vendor Id */
			/* No specific length validation required */
			break;

		case SCMD_INQUIRY_VPD_TYPE_EUI: /* EUI 64 ID */
			/* EUI-64: size is expected to be 8, 12, or 16 bytes */
			if ((dlen != 8) && (dlen != 12) && (dlen != 16)) {
				/* page83 validation failed-EIU64 */
				return (DEVID_RET_INVALID);
			}
			break;

		case SCMD_INQUIRY_VPD_TYPE_NAA: /* NAA Id type */

			/*
			 * the size for this varies -
			 * IEEE extended/registered is 8 bytes
			 * IEEE Registered extended is 16 bytes
			 */
			switch (dblk[4] & 0xf0) {

				case 0x20: /* IEEE Ext */
				case 0x50: /* IEEE Reg */
					if (dlen != 8) {
						/* failed-IEE E/R len */
						return (DEVID_RET_INVALID);
					}
					/*
					 * the codeSet for this MUST
					 * be set to 1
					 */
					if ((dblk[0] & 0x0f) != 1) {
						/*
						 * failed-IEEE E/R
						 * codeSet != 1.
						 */
						return (DEVID_RET_INVALID);
					}
				break;

				case 0x60: /* IEEE EXT REG */
					if (dlen != 16) {
						/* failed-IEEE ER len */
						return (DEVID_RET_INVALID);
					}
					/*
					 * the codeSet for this MUST
					 * be set to 1
					 */
					if ((dblk[0] & 0x0f) != 1) {
						/*
						 * failed-IEEE ER
						 * codeSet != 1.
						 */
						return (DEVID_RET_INVALID);
						}
				break;

				default:
					/* reserved values */
					break;
			}
			break;

		case SCMD_INQUIRY_VPD_TYPE_RTP: /* Relative Target port */
			if (dlen != 4) {
				/* failed-Rel target Port length */
				return (DEVID_RET_INVALID);
			}
			break;

		case SCMD_INQUIRY_VPD_TYPE_TPG: /* Target port group */
			if (dlen != 4) {
				/* failed-target Port group length */
				return (DEVID_RET_INVALID);
			}
			break;

		case SCMD_INQUIRY_VPD_TYPE_LUG: /* Logical unit group */
			if (dlen != 4) {
				/* failed-Logical Unit group length */
				return (DEVID_RET_INVALID);
			}
			break;

		case SCMD_INQUIRY_VPD_TYPE_MD5: /* MD5 unit group */
			if (dlen != 16) {
				/* failed-MD5 Unit grp */
				return (DEVID_RET_INVALID);
			}
			break;

		default:
			break;
		}

		/*
		 * Now lets advance to the next descriptor block
		 * and validate it.
		 * the descriptor block size is <descr Header> + <descr Data>
		 * <descr Header> is equal to 4 bytes
		 * <descr Data> is available in dlen or dblk[3].
		 */
		dblk = &dblk[4 + dlen];

		/*
		 * update the covered_desc_len so that we can ensure that
		 * the 'while' loop terminates.
		 */
		covered_desc_len += (dlen + 4);
	}
	return (DEVID_RET_VALID);
}


/*
 *    Function: is_initialized_id
 *
 * Description: Routine to ensure that the ID calculated is not a
 *		space or zero filled ID. Returning a space / zero
 *		filled ID when the luns on the target are not fully
 *		initialized is a valid response from the target as
 *		per the T10 spec. When a space/zero filled ID is
 *		found its information needs to be polled again
 *		after sometime time to see if the luns are fully
 *		initialized to return a valid guid information.
 *
 *   Arguments: id - raw id
 *              id_len - raw id len
 *
 * Return Code:	DEVID_VALID - indicates a non space/zero filled id
 *		DEVID_INVALID - indicates id contains uninitialized data
 *		and suggests retry of the collection commands.
 */
static int
is_initialized_id(uchar_t *id, size_t id_len)
{
	int idx;

	if ((id == NULL) ||
	    (id_len == 0)) {
		/* got id length as 0 fetch info again */
		return (DEVID_RET_INVALID);
	}

	/* First lets check if the guid is filled with spaces */
	for (idx = 0; idx < id_len; idx++) {
		if (id[idx] != ' ') {
			break;
		}
	}

	/*
	 * Lets exit if we find that it contains ALL spaces
	 * saying that it has an uninitialized guid
	 */
	if (idx >= id_len) {
		/* guid filled with spaces found */
		return (DEVID_RET_INVALID);
	}

	/*
	 * Since we have found that it is not filled with spaces
	 * now lets ensure that the guid is not filled with only
	 * zeros.
	 */
	for (idx = 0; idx < id_len; idx ++) {
		if (id[idx] != 0) {
			return (DEVID_RET_VALID);
		}
	}

	/* guid filled with zeros found */
	return (DEVID_RET_INVALID);
}


/*
 *    Function: is_page80_data_valid
 *
 * Description: This routine is used to validate the page 0x80 data
 *		passed in valid based on the standards specification.
 *
 *   Arguments: inq80 -
 *		inq80_len -
 *
 * Return Code: DEVID_RET_VALID
 *              DEVID_RET_INVALID
 *
 */
/* ARGSUSED */
static int
is_page80_data_valid(uchar_t *inq80, size_t inq80_len)
{
	DEVID_ASSERT(inq80);

	/* if not large enough fail */
	if (inq80_len < SCMD_MIN_INQUIRY_PAGE80_SIZE) {
		return (DEVID_RET_INVALID);
	}

	/*
	 * (inq80_len - 4) is the size of the buffer space available
	 * for the product serial number.  So inq80[3] (ie. product
	 * serial number) should be <= (inq80_len -4).
	 */
	if (inq80[3] > (inq80_len - 4)) {
		return (DEVID_RET_INVALID);
	}

	return (DEVID_RET_VALID);
}


/*
 *    Function: encode_devid_page
 *
 * Description: This routine finds the unique devid if available and
 *		fills the devid and length parameters.
 *
 *   Arguments: version - encode version
 *		inq83 - driver soft state (unit) structure
 *		inq83_len - length of raw inq83 data
 *		id - raw id
 *		id_len - len of raw id
 *		id_type - type of id
 *
 *        Note: DEVID_NONE is returned in the id_type field
 *		if no supported page 83 id is found.
 */
static void
encode_scsi3_page83(int version, uchar_t *inq83, size_t inq83_len,
    uchar_t **id, size_t *id_len, ushort_t *id_type)
{
	size_t	descriptor_bytes_left   = 0;
	size_t	offset			= 0;
	int	idx			= 0;
	size_t	offset_id_type[4];

	DEVID_ASSERT(inq83 != NULL);
	/* inq83 length was already validate in is_page83_valid */
	DEVID_ASSERT(id != NULL);
	DEVID_ASSERT(id_len != NULL);
	DEVID_ASSERT(id_type != NULL);

	/* preset defaults */
	*id = NULL;
	*id_len = 0;
	*id_type = DEVID_NONE;

	/* verify we have enough memory for a ident header */
	if (inq83_len < SCMD_INQUIRY_PAGE83_HDR_SIZE) {
		return;
	}

	/*
	 * Attempt to validate the page data.  Once validated, we'll walk
	 * the descriptors, looking for certain identifier types that will
	 * mark this device with a unique id/wwn.  Note the comment below
	 * for what we really want to receive.
	 */

	/*
	 * The format of the inq83 data (Device Identification VPD page) is
	 * a header (containing the total length of the page, from which
	 * descriptor_bytes_left is calculated), followed by a list of
	 * identification descriptors. Each identifcation descriptor has a
	 * header which includes the length of the individual identification
	 * descriptor).
	 *
	 * Set the offset to the beginning byte of the first identification
	 * descriptor.  We'll index everything from there.
	 */
	offset = SCMD_INQUIRY_PAGE83_HDR_SIZE;
	descriptor_bytes_left = (size_t)((inq83[2] << 8) | inq83[3]);

	/*
	 * If the raw data states that the data is larger
	 * than what is actually received abort encode.
	 * Otherwise we will run off into unknown memory
	 * on the decode.
	 */
	if ((descriptor_bytes_left + offset) > inq83_len) {
		return;
	}


	/* Zero out our offset array */
	bzero(offset_id_type, sizeof (offset_id_type));

	/*
	 * According to the scsi spec 8.4.3 SPC-2, there could be several
	 * descriptors associated with each lun.  Some we care about and some
	 * we don't.  This loop is set up to iterate through the descriptors.
	 * We want the 0x03 case which represents an FC-PH, FC-PH3 or FC-FS
	 * Name_Identifier.  The spec mentions nothing about ordering, so we
	 * don't assume any.
	 *
	 * We need to check if we've finished walking the list of descriptors,
	 * we also perform additional checks to be sure the newly calculated
	 * offset is within the bounds of the buffer, and the identifier length
	 * (as calculated by the length field in the header) is valid. This is
	 * done to protect against devices which return bad page83 data.
	 */
	while ((descriptor_bytes_left > 0) && (offset_id_type[3] == 0) &&
	    (offset + SCMD_INQUIRY_PAGE83_IDENT_DESC_HDR_SIZE <= inq83_len) &&
	    (offset + SCMD_INQUIRY_PAGE83_IDENT_DESC_HDR_SIZE +
	    (size_t)inq83[offset + 3] <= inq83_len)) {
		/*
		 * Inspect the Identification descriptor list. Store the
		 * offsets in the devid page separately for 0x03, 0x01 and
		 * 0x02.  Identifiers 0x00 and 0x04 are not useful as they
		 * don't represent unique identifiers for a lun.  We also
		 * check the association by masking with 0x3f because we want
		 * an association of 0x0 - indicating the identifier field is
		 * associated with the addressed physical or logical device
		 * and not the port.
		 */
		switch ((inq83[offset + 1] & 0x3f)) {
		case SCMD_INQUIRY_VPD_TYPE_T10:
			offset_id_type[SCMD_INQUIRY_VPD_TYPE_T10] = offset;
			break;
		case SCMD_INQUIRY_VPD_TYPE_EUI:
			offset_id_type[SCMD_INQUIRY_VPD_TYPE_EUI] = offset;
			break;
		case SCMD_INQUIRY_VPD_TYPE_NAA:
			offset_id_type[SCMD_INQUIRY_VPD_TYPE_NAA] = offset;
			break;
		default:
			/* Devid page undesired id type */
			break;
		}
		/*
		 * Calculate the descriptor bytes left and move to
		 * the beginning byte of the next id descriptor.
		 */
		descriptor_bytes_left -= (size_t)(inq83[offset + 3] +
		    SCMD_INQUIRY_PAGE83_IDENT_DESC_HDR_SIZE);
		offset += (SCMD_INQUIRY_PAGE83_IDENT_DESC_HDR_SIZE +
		    (size_t)inq83[offset + 3]);
	}

	offset = 0;

	/*
	 * We can't depend on an order from a device by identifier type, but
	 * once we have them, we'll walk them in the same order to prevent a
	 * firmware upgrade from breaking our algorithm.  Start with the one
	 * we want the most: id_offset_type[3].
	 */
	for (idx = 3; idx > 0; idx--) {
		if (offset_id_type[idx] > 0) {
			offset = offset_id_type[idx];
			break;
		}
	}

	/*
	 * We have a valid Device ID page, set the length of the
	 * identifier and copy the value into the wwn.
	 */
	if (offset > 0) {
		*id_len = (size_t)inq83[offset + 3];
		if ((*id = DEVID_MALLOC(*id_len)) == NULL) {
			*id_len = 0;
			return;
		}
		bcopy(&inq83[offset + SCMD_INQUIRY_PAGE83_IDENT_DESC_HDR_SIZE],
		    *id, *id_len);

		/* set devid type */
		switch (version) {
		/* In version 1 all page 83 types were grouped */
		case DEVID_SCSI_ENCODE_VERSION1:
			*id_type = DEVID_SCSI3_WWN;
			break;
		/* In version 2 we break page 83 apart to be unique */
		case DEVID_SCSI_ENCODE_VERSION2:
			switch (idx) {
			case 3:
				*id_type = DEVID_SCSI3_VPD_NAA;
				break;
			case 2:
				*id_type = DEVID_SCSI3_VPD_EUI;
				break;
			case 1:
				*id_type = DEVID_SCSI3_VPD_T10;
				break;
			default:
				DEVID_FREE(*id, *id_len);
				*id_len = 0;
				break;
			}
			break;
		default:
			DEVID_FREE(*id, *id_len);
			*id_len = 0;
			break;
		}
	}
}


/*
 *    Function: encode_scsi3_page83_emc
 *
 * Description: Routine to handle proprietary page 83 of EMC Symmetrix
 *              device. Called by ssfcp_handle_page83()
 *
 *   Arguments: version - encode version
 *		inq83 - scsi page 83 buffer
 *		inq83_len - scsi page 83 buffer size
 *		id - raw emc id
 *		id_len - len of raw emc id
 *		id_type - type of emc id
 */
static void
encode_scsi3_page83_emc(int version, uchar_t *inq83,
    size_t inq83_len, uchar_t **id, size_t *id_len, ushort_t *id_type)
{
	uchar_t	*guidp	= NULL;

	DEVID_ASSERT(inq83 != NULL);
	DEVID_ASSERT(id != NULL);
	DEVID_ASSERT(id_len != NULL);
	DEVID_ASSERT(id_type != NULL);

	/* preset defaults */
	*id = NULL;
	*id_len = 0;
	*id_type = DEVID_NONE;

	/* The initial devid algorithm didn't use EMC page 83 data */
	if (version == DEVID_SCSI_ENCODE_VERSION1) {
		return;
	}

	/* EMC page 83 requires atleast 20 bytes */
	if (inq83_len < (SCMD_INQUIRY_PAGE83_HDR_SIZE +
	    SCSI_INQUIRY_PAGE83_EMC_SYMMETRIX_ID_LEN)) {
		return;
	}

	/*
	 * The 4th byte in the page 83 info returned is most likely
	 * indicating the length of the id - which 0x10(16 bytes)
	 * and the 5th byte is indicating that the id is of
	 * IEEE Registered Extended Name format(6). Validate
	 * these code prints before proceeding further as the
	 * following proprietary approach is tied to the specific
	 * device type and incase the EMC firmware changes, we will
	 * have to validate for the changed device before we start
	 * supporting such a device.
	 */
	if ((inq83[3] != 0x10) || (inq83[4] != 0x60)) {
		/* unsupported emc symtx device type */
		return;
	} else {
		guidp = &inq83[SCMD_INQUIRY_PAGE83_HDR_SIZE];
		/*
		 * The GUID returned by the EMC device is
		 * in the IEEE Registered Extended Name format(6)
		 * as a result it is of 16 bytes in length.
		 * An IEEE Registered Name format(5) will be of
		 * 8 bytes which is NOT what is being returned
		 * by the device type for which we are providing
		 * the support.
		 */
		*id_len = SCSI_INQUIRY_PAGE83_EMC_SYMMETRIX_ID_LEN;
		if ((*id = DEVID_MALLOC(*id_len)) == NULL) {
			*id_len = 0;
			return;
		}
		bcopy(guidp, *id, *id_len);

		/* emc id matches type 3 */
		*id_type = DEVID_SCSI3_VPD_NAA;
	}
}


/*
 *    Function: encode_serialnum
 *
 * Description: This routine finds the unique devid from the inquiry page
 *		0x80, serial number page.  If available and fills the wwn
 *		and length parameters.
 *
 *   Arguments: version - encode version
 *		inq - standard inquiry data
 *		inq80 - serial inquiry data
 *		inq80_len - serial inquiry data len
 *		id - raw id
 *		id_len - raw id len
 *		id_type - raw id type
 */
/* ARGSUSED */
static void
encode_serialnum(int version, uchar_t *inq, uchar_t *inq80,
    size_t inq80_len, uchar_t **id, size_t *id_len, ushort_t *id_type)
{
	struct scsi_inquiry	*inq_std	= (struct scsi_inquiry *)inq;
	int			idx		= 0;

	DEVID_ASSERT(inq != NULL);
	DEVID_ASSERT(inq80 != NULL);
	DEVID_ASSERT(id != NULL);
	DEVID_ASSERT(id_len != NULL);
	DEVID_ASSERT(id_type != NULL);

	/* preset defaults */
	*id = NULL;
	*id_len = 0;
	*id_type = DEVID_NONE;

	/* verify inq80 buffer is large enough for a header */
	if (inq80_len < SCMD_MIN_INQUIRY_PAGE80_SIZE) {
		return;
	}

	/*
	 * Attempt to validate the page data.  Once validated, we'll check
	 * the serial number.
	 */
	*id_len = (size_t)inq80[3]; /* Store Product Serial Number length */

	/* verify buffer is large enough for serial number */
	if (inq80_len < (*id_len + SCMD_MIN_INQUIRY_PAGE80_SIZE)) {
		return;
	}

	/*
	 * Device returns ASCII space (20h) in all the bytes of successful data
	 * transfer, if the product serial number is not available.  So we end
	 * up having to check all the bytes for a space until we reach
	 * something else.
	 */
	for (idx = 0; idx < *id_len; idx++) {
		if (inq80[4 + idx] == ' ') {
			continue;
		}
		/*
		 * The serial number is valid, but since this is only vendor
		 * unique, we'll combine the inquiry vid and pid with the
		 * serial number.
		 */
		*id_len += sizeof (inq_std->inq_vid);
		*id_len += sizeof (inq_std->inq_pid);

		if ((*id = DEVID_MALLOC(*id_len)) == NULL) {
			*id_len = 0;
			return;
		}

		bcopy(&inq_std->inq_vid, *id, sizeof (inq_std->inq_vid));
		bcopy(&inq_std->inq_pid, &(*id)[sizeof (inq_std->inq_vid)],
		    sizeof (inq_std->inq_pid));
		bcopy(&inq80[4], &(*id)[sizeof (inq_std->inq_vid) +
		    sizeof (inq_std->inq_pid)], inq80[3]);

		*id_type = DEVID_SCSI_SERIAL;
		break;
	}

	/*
	 * The spec suggests that the command could succeed but return all
	 * spaces if the product serial number is not available.  In this case
	 * we need to fail this routine. To accomplish this, we compare our
	 * length to the serial number length. If they are the same, then we
	 * never copied in the vid and updated the length. That being the case,
	 * we must not have found a valid serial number.
	 */
	if (*id_len == (size_t)inq80[3]) {
		/* empty unit serial number */
		if (*id != NULL) {
			DEVID_FREE(*id, *id_len);
		}
		*id = NULL;
		*id_len = 0;
	}
}


/*
 *    Function: encode_sun_serialnum
 *
 * Description: This routine finds the unique devid from the inquiry page
 *		0x80, serial number page.  If available and fills the wwn
 *		and length parameters.
 *
 *   Arguments: version - encode version
 *		inq - standard inquiry data
 *		inq_len - standard inquiry data len
 *		id - raw id
 *		id_len - raw id len
 *		id_type - raw id type
 *
 * Return Code: DEVID_SUCCESS
 *              DEVID_FAILURE
 */
/* ARGSUSED */
static void
encode_sun_serialnum(int version, uchar_t *inq,
    size_t inq_len, uchar_t **id, size_t *id_len, ushort_t *id_type)
{
	struct scsi_inquiry *inq_std = (struct scsi_inquiry *)inq;

	DEVID_ASSERT(inq != NULL);
	DEVID_ASSERT(id != NULL);
	DEVID_ASSERT(id_len != NULL);
	DEVID_ASSERT(id_type != NULL);

	/* verify enough buffer is available */
	if (inq_len < SCMD_MIN_STANDARD_INQUIRY_SIZE) {
		return;
	}

	/* sun qual drive */
	if ((inq_std != NULL) &&
	    (bcmp(&inq_std->inq_pid[SCSI_INQUIRY_VID_POS],
	    SCSI_INQUIRY_VID_SUN, SCSI_INQUIRY_VID_SUN_LEN) == 0)) {
		/*
		 * VPD pages 0x83 and 0x80 are unavailable. This
		 * is a Sun qualified disk as indicated by
		 * "SUN" in bytes 25-27 of the inquiry data
		 * (bytes 9-11 of the pid).  Devid's are created
		 * for Sun qualified disks by combining the
		 * vendor id with the product id with the serial
		 * number located in bytes 36-47 of the inquiry data.
		 */

		/* get data size */
		*id_len = sizeof (inq_std->inq_vid) +
		    sizeof (inq_std->inq_pid) +
		    sizeof (inq_std->inq_serial);

		if ((*id = DEVID_MALLOC(*id_len)) == NULL) {
			*id_len = 0;
			return;
		}

		/* copy the vid at the beginning */
		bcopy(&inq_std->inq_vid, *id,
		    sizeof (inq_std->inq_vid));

		/* copy the pid after the vid */
		bcopy(&inq_std->inq_pid,
		    &(*id)[sizeof (inq_std->inq_vid)],
		    sizeof (inq_std->inq_pid));

		/* copy the serial number after the vid and pid */
		bcopy(&inq_std->inq_serial,
		    &(*id)[sizeof (inq_std->inq_vid) +
		    sizeof (inq_std->inq_pid)],
		    sizeof (inq_std->inq_serial));

		/* devid formed from inquiry data */
		*id_type = DEVID_SCSI_SERIAL;
	}
}


/*
 *    Function: devid_scsi_init
 *
 * Description: This routine is used to create a devid for a scsi
 *		devid type.
 *
 *   Arguments: hint - driver soft state (unit) structure
 *		raw_id - pass by reference variable to hold wwn
 *		raw_id_len - wwn length
 *		raw_id_type -
 *		ret_devid -
 *
 * Return Code: DEVID_SUCCESS
 *              DEVID_FAILURE
 *
 */
static int
devid_scsi_init(
	char		*driver_name,
	uchar_t		*raw_id,
	size_t		raw_id_len,
	ushort_t	raw_id_type,
	ddi_devid_t	*ret_devid)
{
	impl_devid_t	*i_devid	= NULL;
	int		i_devid_len	= 0;
	int		driver_name_len	= 0;
	ushort_t	u_raw_id_len	= 0;

	DEVID_ASSERT(raw_id != NULL);
	DEVID_ASSERT(ret_devid != NULL);

	if (!IS_DEVID_SCSI_TYPE(raw_id_type)) {
		*ret_devid = NULL;
		return (DEVID_FAILURE);
	}

	i_devid_len = sizeof (*i_devid) + raw_id_len - sizeof (i_devid->did_id);
	if ((i_devid = DEVID_MALLOC(i_devid_len)) == NULL) {
		*ret_devid = NULL;
		return (DEVID_FAILURE);
	}

	i_devid->did_magic_hi = DEVID_MAGIC_MSB;
	i_devid->did_magic_lo = DEVID_MAGIC_LSB;
	i_devid->did_rev_hi = DEVID_REV_MSB;
	i_devid->did_rev_lo = DEVID_REV_LSB;
	DEVID_FORMTYPE(i_devid, raw_id_type);
	u_raw_id_len = raw_id_len;
	DEVID_FORMLEN(i_devid, u_raw_id_len);

	/* Fill in driver name hint */
	bzero(i_devid->did_driver, DEVID_HINT_SIZE);
	if (driver_name != NULL) {
		driver_name_len = strlen(driver_name);
		if (driver_name_len > DEVID_HINT_SIZE) {
			/* Pick up last four characters of driver name */
			driver_name += driver_name_len - DEVID_HINT_SIZE;
			driver_name_len = DEVID_HINT_SIZE;
		}
		bcopy(driver_name, i_devid->did_driver, driver_name_len);
	}

	bcopy(raw_id, i_devid->did_id, raw_id_len);

	/* return device id */
	*ret_devid = (ddi_devid_t)i_devid;
	return (DEVID_SUCCESS);
}


/*
 *    Function: devid_to_guid
 *
 * Description: This routine extracts a guid string form a devid.
 *		The common use of this guid is for a HBA driver
 *		to pass into mdi_pi_alloc().
 *
 *   Arguments: devid - devid to extract guid from
 *
 * Return Code: guid string - success
 *		NULL - failure
 */
char *
#ifdef  _KERNEL
ddi_devid_to_guid(ddi_devid_t devid)
#else   /* !_KERNEL */
devid_to_guid(ddi_devid_t devid)
#endif  /* _KERNEL */
{
	impl_devid_t	*id	= (impl_devid_t *)devid;
	int		len	= 0;
	int		idx	= 0;
	int		num	= 0;
	char		*guid	= NULL;
	char		*ptr	= NULL;
	char		*dp	= NULL;

	DEVID_ASSERT(devid != NULL);

	/* NULL devid -> NULL guid */
	if (devid == NULL)
		return (NULL);

	if (!IS_DEVID_GUID_TYPE(DEVID_GETTYPE(id)))
		return (NULL);

	/* guid is always converted to ascii, append NULL */
	len = DEVID_GETLEN(id);

	/* allocate guid string */
	if ((guid = DEVID_MALLOC((len * 2) + 1)) == NULL)
		return (NULL);

	/* perform encode of id to hex string */
	ptr = guid;
	for (idx = 0, dp = &id->did_id[0]; idx < len; idx++, dp++) {
		num = ((*dp) >> 4) & 0xF;
		*ptr++ = (num < 10) ? (num + '0') : (num + ('a' - 10));
		num = (*dp) & 0xF;
		*ptr++ = (num < 10) ? (num + '0') : (num + ('a' - 10));
	}
	*ptr = 0;

	return (guid);
}

/*
 *    Function: devid_free_guid
 *
 * Description: This routine frees a guid allocated by
 *		devid_to_guid().
 *
 *   Arguments: guid - guid to free
 */
void
#ifdef  _KERNEL
ddi_devid_free_guid(char *guid)
#else   /* !_KERNEL */
devid_free_guid(char *guid)
#endif  /* _KERNEL */
{
	if (guid != NULL) {
		DEVID_FREE(guid, strlen(guid) + 1);
	}
}

static char
ctoi(char c)
{
	if ((c >= '0') && (c <= '9'))
		c -= '0';
	else if ((c >= 'A') && (c <= 'F'))
		c = c - 'A' + 10;
	else if ((c >= 'a') && (c <= 'f'))
		c = c - 'a' + 10;
	else
		c = -1;
	return (c);
}

/* ====NOTE: The scsi_* interfaces are not related to devids :NOTE==== */

/*
 *    Function: scsi_wwnstr_to_wwn
 *
 * Description: This routine translates wwn from wwnstr string to uint64 wwn.
 *
 *   Arguments: wwnstr - the string wwn to be transformed
 *              wwnp - the pointer to 64 bit wwn
 */
int
scsi_wwnstr_to_wwn(const char *wwnstr, uint64_t *wwnp)
{
	int		i;
	char		cl, ch;
	uint64_t	tmp;

	if (wwnp == NULL)
		return (DDI_FAILURE);
	*wwnp = 0;

	if (wwnstr == NULL)
		return (DDI_FAILURE);

	/* Skip leading 'w' if wwnstr is in unit-address form */
	wwnstr = scsi_wwnstr_skip_ua_prefix(wwnstr);

	if (strlen(wwnstr) != 16)
		return (DDI_FAILURE);

	for (i = 0; i < 8; i++) {
		ch = ctoi(*wwnstr++);
		cl = ctoi(*wwnstr++);
		if (cl == -1 || ch == -1) {
			return (DDI_FAILURE);
		}
		tmp = (ch << 4) + cl;
		*wwnp = (*wwnp << 8) | tmp;
	}
	return (DDI_SUCCESS);
}

/*
 *    Function: scsi_wwn_to_wwnstr
 *
 * Description: This routine translates from a uint64 wwn to a wwnstr
 *
 *   Arguments:
 *              wwn - the 64 bit wwn
 *		unit_address_form - do we want a leading 'w'?
 *		wwnstr - allow caller to perform wwnstr allocation.
 *			If non-NULL, don't use scsi_free_wwnstr(),
 *			and make sure you provide 18/17 bytes of  space.
 */
char *
scsi_wwn_to_wwnstr(uint64_t wwn, int unit_address_form, char *wwnstr)
{
	int	len;

	/* make space for leading 'w' */
	if (unit_address_form)
		len = 1 + 16 + 1;	/* "w0123456789abcdef\0" */
	else
		len = 16 + 1;		/* "0123456789abcdef\0" */

	if (wwnstr == NULL) {
		/* We allocate, caller uses scsi_free_wwnstr(). */
		if ((wwnstr = DEVID_MALLOC(len)) == NULL)
			return (NULL);
	}

	if (unit_address_form)
		(void) snprintf(wwnstr, len, "w%016" PRIx64, wwn);
	else
		(void) snprintf(wwnstr, len, "%016" PRIx64, wwn);
	return (wwnstr);
}

/*
 *    Function: scsi_wwnstr_hexcase
 *
 * Description: This routine switches a wwnstr to upper/lower case hex
 *		(a wwnstr uses lower-case hex by default).
 *
 *   Arguments:
 *              wwnstr - the pointer to the wwnstr string.
 *		upper_case_hex - non-zero will convert to upper_case hex
 *			zero will convert to lower case hex.
 */
void
scsi_wwnstr_hexcase(char *wwnstr, int upper_case_hex)
{
	char	*s;
	char	c;

	for (s = wwnstr; *s; s++) {
		c = *s;
		if ((upper_case_hex != 0) &&
		    ((c >= 'a') && (c <= 'f')))
			c -= ('a' - 'A');	/* lower to upper */
		else if ((upper_case_hex == 0) &&
		    ((c >= 'A') && (c <= 'F')))
			c += ('a' - 'A');	/* upper to lower */
		*s = c;
	}
}

/*
 * Function: scsi_wwnstr_skip_ua_prefix
 *
 * Description: This routine removes the leading 'w' in wwnstr,
 * 		if its in unit-address form.
 *
 * Arguments: wwnstr - the string wwn to be transformed
 *
 */
const char *
scsi_wwnstr_skip_ua_prefix(const char *wwnstr)
{
	if (*wwnstr == 'w')
		wwnstr++;
	return (wwnstr);
}

/*
 *    Function: scsi_wwnstr_free
 *
 * Description: This routine frees a wwnstr returned by a call
 *		to scsi_wwn_to_strwwn with a NULL wwnstr argument.
 *
 *   Arguments:
 *              wwnstr - the pointer to the wwnstr string to free.
 */
void
scsi_free_wwnstr(char *wwnstr)
{
#ifdef	_KERNEL
	kmem_free(wwnstr, strlen(wwnstr) + 1);
#else	/* _KERNEL */
	free(wwnstr);
#endif	/* _KERNEL */
}

/*
 *    Function: scsi_lun_to_lun64/scsi_lun64_to_lun
 *
 * Description: Convert between normalized (SCSI-3) LUN format, as
 *		described by scsi_lun_t, and a normalized lun64_t
 *              representation (used by Solaris SCSI_ADDR_PROP_LUN64
 *		"lun64" property). The normalized representation maps
 *		in a compatible way to SCSI-2 LUNs. See scsi_address.h
 *
 *              SCSI-3 LUNs are 64 bits. SCSI-2 LUNs are 3 bits (up to
 *              5 bits in non-compliant implementations). SCSI-3 will
 *              pass a (64-bit) scsi_lun_t, but we need a
 *              representation from which we can for example, make
 *              device names. For unit-address compatibility, we represent
 *		64-bit LUN numbers in such a way that they appear like they
 *		would have under SCSI-2. This means that the single level
 *              LUN number is in the lowest byte with the second,
 *              third, and fourth level LUNs represented in
 *              successively higher bytes. In particular, if (and only
 *              if) the first byte of a 64 bit LUN is zero, denoting
 *              "Peripheral Device Addressing Method" and "Bus
 *              Identifier" zero, then the target implements LUNs
 *              compatible in spirit with SCSI-2 LUNs (although under
 *              SCSI-3 there may be up to 256 of them). Under SCSI-3
 *              rules, a target is *required* to use this format if it
 *              contains 256 or fewer Logical Units, none of which are
 *              dependent logical units. These routines have knowledge
 *		of the structure and size of a scsi_lun_t.
 *
 * NOTE: We tolerate vendors that use "Single level LUN structure using
 * peripheral device addressing method" with a non-zero bus identifier
 * (spec says bus identifier must be zero).  Described another way, we let
 * the non-'addressing method' bits of sl_lun1_msb contribute to our lun64
 * value).
 */
scsi_lun64_t
scsi_lun_to_lun64(scsi_lun_t lun)
{
	scsi_lun64_t    lun64;

	/*
	 * Check to see if we have a single level lun that uses the
	 * "Peripheral Device" addressing method. If so, the lun64 value is
	 * kept in Solaris 'unit-address compatibility' form.
	 */
	if (((lun.sl_lun2_msb == 0) && (lun.sl_lun2_lsb == 0) &&
	    (lun.sl_lun3_msb == 0) && (lun.sl_lun3_lsb == 0) &&
	    (lun.sl_lun4_msb == 0) && (lun.sl_lun4_lsb == 0)) &&
	    ((lun.sl_lun1_msb & SCSI_LUN_AM_MASK) == SCSI_LUN_AM_PDEV)) {
		/*
		 * LUN has Solaris 'unit-address compatibility' form, construct
		 * lun64 value from non-'addressing method' bits of msb and lsb.
		 */
		lun64 = ((lun.sl_lun1_msb & ~SCSI_LUN_AM_MASK) << 8) |
		    lun.sl_lun1_lsb;
	} else {
		/*
		 * LUN does not have a Solaris 'unit-address compatibility'
		 * form, construct lun64 value in full 64 bit LUN format.
		 */
		lun64 =
		    ((scsi_lun64_t)lun.sl_lun1_msb << 56) |
		    ((scsi_lun64_t)lun.sl_lun1_lsb << 48) |
		    ((scsi_lun64_t)lun.sl_lun2_msb << 40) |
		    ((scsi_lun64_t)lun.sl_lun2_lsb << 32) |
		    ((scsi_lun64_t)lun.sl_lun3_msb << 24) |
		    ((scsi_lun64_t)lun.sl_lun3_lsb << 16) |
		    ((scsi_lun64_t)lun.sl_lun4_msb <<  8) |
		    (scsi_lun64_t)lun.sl_lun4_lsb;
	}
	return (lun64);
}

scsi_lun_t
scsi_lun64_to_lun(scsi_lun64_t lun64)
{
	scsi_lun_t	lun;

	if (lun64 <= (((0xFF & ~SCSI_LUN_AM_MASK) << 8) | 0xFF)) {
		/*
		 * lun64 is in Solaris 'unit-address compatibility' form.
		 */
		lun.sl_lun1_msb = SCSI_LUN_AM_PDEV | (lun64 >> 8);
		lun.sl_lun1_lsb = (uchar_t)lun64;
		lun.sl_lun2_msb = 0;
		lun.sl_lun2_lsb = 0;
		lun.sl_lun3_msb = 0;
		lun.sl_lun3_lsb = 0;
		lun.sl_lun4_msb = 0;
		lun.sl_lun4_lsb = 0;
	} else {
		/* lun64 is in full 64 bit LUN format. */
		lun.sl_lun1_msb = (uchar_t)(lun64 >> 56);
		lun.sl_lun1_lsb = (uchar_t)(lun64 >> 48);
		lun.sl_lun2_msb = (uchar_t)(lun64 >> 40);
		lun.sl_lun2_lsb = (uchar_t)(lun64 >> 32);
		lun.sl_lun3_msb = (uchar_t)(lun64 >> 24);
		lun.sl_lun3_lsb = (uchar_t)(lun64 >> 16);
		lun.sl_lun4_msb = (uchar_t)(lun64 >>  8);
		lun.sl_lun4_lsb = (uchar_t)(lun64);
	}
	return (lun);
}

/*
 * This routine returns the true length of the ascii inquiry fields that are to
 * be created by removing the padded spaces at the end of the inquiry data.
 * This routine was designed for trimming spaces from the vid, pid and revision
 * which are defined as being left aligned.  In addition, we return 0 length
 * if the field is full of all 0's or spaces, indicating to the caller that
 * the device was not ready to return the inquiry data as per note 65 in
 * the scsi-2 spec.
 */
int
scsi_ascii_inquiry_len(char *field, size_t length)
{
	int retval;
	int trailer;
	char *p;

	retval = length;

	/*
	 * The vid, pid and revision are left-aligned ascii fields within the
	 * inquiry data.  Here we trim the end of these fields by discounting
	 * length associated with trailing spaces or NULL bytes.  The remaining
	 * bytes shall be only graphics codes - 0x20 through 0x7e as per the
	 * scsi spec definition.  If we have all 0's or spaces, we return 0
	 * length.  For devices that store inquiry data on the device, they
	 * can return 0's or spaces in these fields until the data is avail-
	 * able from the device (See NOTE 65 in the scsi-2 specification
	 * around the inquiry command.)  We don't want to create a field in
	 * the case of a device not able to return valid data.
	 */
	trailer = 1;
	for (p = field + length - 1; p >= field; p--) {
		if (trailer) {
			if ((*p == ' ') || (*p == '\0')) {
				retval--;
				continue;
			}
			trailer = 0;
		}

		/* each char must be within 0x20 - 0x7e */
		if (*p < 0x20 || *p > 0x7e) {
			retval = -1;
			break;
		}

	}

	return (retval);
}