/*
* 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);
}