OpenSolaris_b135/common/devid/devid.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, Version 1.0 only
 * (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 2004 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/stropts.h>
#include <sys/debug.h>
#include <sys/isa_defs.h>
#include <sys/dditypes.h>
#include <sys/ddi_impldefs.h>
#include "devid_impl.h"

static int devid_str_decode_id(char *devidstr, ddi_devid_t *devidp,
    char **minor_namep, impl_devid_t *id);


/*
 * Validate device id.
 */
int
#ifdef	_KERNEL
ddi_devid_valid(ddi_devid_t devid)
#else	/* !_KERNEL */
devid_valid(ddi_devid_t devid)
#endif	/* _KERNEL */
{
	impl_devid_t	*id = (impl_devid_t *)devid;
	ushort_t	type;

	DEVID_ASSERT(devid != NULL);

	if (id->did_magic_hi != DEVID_MAGIC_MSB)
		return (DEVID_RET_INVALID);

	if (id->did_magic_lo != DEVID_MAGIC_LSB)
		return (DEVID_RET_INVALID);

	if (id->did_rev_hi != DEVID_REV_MSB)
		return (DEVID_RET_INVALID);

	if (id->did_rev_lo != DEVID_REV_LSB)
		return (DEVID_RET_INVALID);

	type = DEVID_GETTYPE(id);
	if ((type == DEVID_NONE) || (type > DEVID_MAXTYPE))
		return (DEVID_RET_INVALID);

	return (DEVID_RET_VALID);
}

/*
 * Return the sizeof a device id. If called with NULL devid it returns
 * the amount of space needed to determine the size.
 */
size_t
#ifdef	_KERNEL
ddi_devid_sizeof(ddi_devid_t devid)
#else	/* !_KERNEL */
devid_sizeof(ddi_devid_t devid)
#endif	/* _KERNEL */
{
	impl_devid_t	*id = (impl_devid_t *)devid;

	if (id == NULL)
		return (sizeof (*id) - sizeof (id->did_id));

	DEVID_ASSERT(DEVID_FUNC(devid_valid)(devid) == DEVID_RET_VALID);

	return (sizeof (*id) + DEVID_GETLEN(id) - sizeof (id->did_id));
}

/*
 * Compare two device id's.
 *	-1 - less than
 *	0  - equal
 * 	1  - greater than
 */
int
#ifdef	_KERNEL
ddi_devid_compare(ddi_devid_t id1, ddi_devid_t id2)
#else	/* !_KERNEL */
devid_compare(ddi_devid_t id1, ddi_devid_t id2)
#endif	/* _KERNEL */
{
	int		rval;
	impl_devid_t	*i_id1	= (impl_devid_t *)id1;
	impl_devid_t	*i_id2	= (impl_devid_t *)id2;
	ushort_t	i_id1_type;
	ushort_t	i_id2_type;

	DEVID_ASSERT((id1 != NULL) && (id2 != NULL));
	DEVID_ASSERT(DEVID_FUNC(devid_valid)(id1) == DEVID_RET_VALID);
	DEVID_ASSERT(DEVID_FUNC(devid_valid)(id2) == DEVID_RET_VALID);

	/* magic and revision comparison */
	if ((rval = bcmp(id1, id2, 4)) != 0) {
		return (rval);
	}

	/* get current devid types */
	i_id1_type = DEVID_GETTYPE(i_id1);
	i_id2_type = DEVID_GETTYPE(i_id2);

	/*
	 * Originaly all page83 devids used DEVID_SCSI3_WWN.
	 * To avoid a possible uniqueness issue each type of page83
	 * encoding supported is represented as a separate
	 * devid type.  If comparing DEVID_SCSI3_WWN against
	 * one of the new page83 encodings we assume that no
	 * uniqueness issue exists (since we had apparently been
	 * running with the old DEVID_SCSI3_WWN encoding without
	 * a problem).
	 */
	if ((i_id1_type == DEVID_SCSI3_WWN) ||
	    (i_id2_type == DEVID_SCSI3_WWN)) {
		/*
		 * Atleast one devid is using old scsi
		 * encode algorithm.  Force devid types
		 * to same scheme for comparison.
		 */
		if (IS_DEVID_SCSI3_VPD_TYPE(i_id1_type)) {
			i_id1_type = DEVID_SCSI3_WWN;
		}
		if (IS_DEVID_SCSI3_VPD_TYPE(i_id2_type)) {
			i_id2_type = DEVID_SCSI3_WWN;
		}
	}

	/* type comparison */
	if (i_id1_type != i_id2_type) {
		return ((i_id1_type < i_id2_type) ? -1 : 1);
	}

	/* length comparison */
	if (DEVID_GETLEN(i_id1) != DEVID_GETLEN(i_id2)) {
		return (DEVID_GETLEN(i_id1) < DEVID_GETLEN(i_id2) ? -1 : 1);
	}

	/* id comparison */
	rval = bcmp(i_id1->did_id, i_id2->did_id, DEVID_GETLEN(i_id1));

	return (rval);
}

/*
 * Free a Device Id
 */
void
#ifdef	_KERNEL
ddi_devid_free(ddi_devid_t devid)
#else	/* !_KERNEL */
devid_free(ddi_devid_t devid)
#endif	/* _KERNEL */
{
	DEVID_ASSERT(devid != NULL);
	DEVID_FREE(devid, DEVID_FUNC(devid_sizeof)(devid));
}

/*
 * Encode a device id into a string.  See ddi_impldefs.h for details.
 */
char *
#ifdef	_KERNEL
ddi_devid_str_encode(ddi_devid_t devid, char *minor_name)
#else	/* !_KERNEL */
devid_str_encode(ddi_devid_t devid, char *minor_name)
#endif	/* _KERNEL */
{
	impl_devid_t	*id = (impl_devid_t *)devid;
	size_t		driver_len, devid_len, slen;
	char		*sbuf, *dsp, *dp, ta;
	int		i, n, ascii;

	/* "id0" is the encoded representation of a NULL device id */
	if (devid == NULL) {
		if ((sbuf = DEVID_MALLOC(4)) == NULL)
			return (NULL);
		*(sbuf+0) = DEVID_MAGIC_MSB;
		*(sbuf+1) = DEVID_MAGIC_LSB;
		*(sbuf+2) = '0';
		*(sbuf+3) = 0;
		return (sbuf);
	}

	/* verify input */
	if (DEVID_FUNC(devid_valid)(devid) != DEVID_RET_VALID)
		return (NULL);

	/* scan the driver hint to see how long the hint is */
	for (driver_len = 0; driver_len < DEVID_HINT_SIZE; driver_len++)
		if (id->did_driver[driver_len] == '\0')
			break;

	/* scan the contained did_id to see if it meets ascii requirements */
	devid_len = DEVID_GETLEN(id);
	for (ascii = 1, i = 0; i < devid_len; i++)
		if (!DEVID_IDBYTE_ISASCII(id->did_id[i])) {
			ascii = 0;
			break;
		}

	/* some types should always go hex even if they look ascii */
	if (DEVID_TYPE_BIN_FORCEHEX(id->did_type_lo))
		ascii = 0;

	/* set the length of the resulting string */
	slen = 2 + 1;					/* <magic><rev> "id1" */
	slen += 1 + driver_len + 1 + 1;			/* ",<driver>@<type>" */
	slen += ascii ? devid_len : (devid_len * 2);	/* did_id field */
	if (minor_name) {
		slen += 1;				/* '/' */
		slen += strlen(minor_name);		/* len of minor_name */
	}
	slen += 1;					/* NULL */

	/* allocate string */
	if ((sbuf = DEVID_MALLOC(slen)) == NULL)
		return (NULL);

	/* perform encode of id to hex string */
	dsp = sbuf;
	*dsp++ = id->did_magic_hi;
	*dsp++ = id->did_magic_lo;
	*dsp++ = DEVID_REV_BINTOASCII(id->did_rev_lo);
	*dsp++ = ',';
	for (i = 0; i < driver_len; i++)
		*dsp++ = id->did_driver[i];
	*dsp++ = '@';
	ta = DEVID_TYPE_BINTOASCII(id->did_type_lo);
	if (ascii)
		ta = DEVID_TYPE_SETASCII(ta);
	*dsp++ = ta;
	for (i = 0, dp = &id->did_id[0]; i < devid_len; i++, dp++) {
		if (ascii) {
			if (*dp == ' ')
				*dsp++ = '_';
			else if (*dp == 0x00)
				*dsp++ = '~';
			else
				*dsp++ = *dp;
		} else {
			n = ((*dp) >> 4) & 0xF;
			*dsp++ = (n < 10) ? (n + '0') : (n + ('a' - 10));
			n = (*dp) & 0xF;
			*dsp++ = (n < 10) ? (n + '0') : (n + ('a' - 10));
		}
	}

	if (minor_name) {
		*dsp++ = '/';
		(void) strcpy(dsp, minor_name);
	} else
		*dsp++ = 0;

	/* ensure that (strlen + 1) is correct length for free */
	DEVID_ASSERT((strlen(sbuf) + 1) == slen);
	return (sbuf);
}

/* free the string returned by devid_str_encode */
void
#ifdef	_KERNEL
ddi_devid_str_free(char *devidstr)
#else	/* !_KERNEL */
devid_str_free(char *devidstr)
#endif	/* _KERNEL */
{
	DEVID_FREE(devidstr, strlen(devidstr) + 1);
}

/*
 * given the string representation of a device id returned by calling
 * devid_str_encode (passed in as devidstr), return pointers to the
 * broken out devid and minor_name as requested. Devidstr remains
 * allocated and unmodified. The devid returned in *devidp should be freed by
 * calling devid_free.  The minor_name returned in minor_namep should
 * be freed by calling devid_str_free(minor_namep).
 *
 * See ddi_impldefs.h for format details.
 */
int
#ifdef	_KERNEL
ddi_devid_str_decode(
#else	/* !_KERNEL */
devid_str_decode(
#endif	/* _KERNEL */
    char *devidstr, ddi_devid_t *devidp, char **minor_namep)
{
	return (devid_str_decode_id(devidstr, devidp, minor_namep, NULL));
}

/* implementation for (ddi_)devid_str_decode */
static int
devid_str_decode_id(char *devidstr, ddi_devid_t *devidp,
    char **minor_namep, impl_devid_t *id)
{
	char		*str, *msp, *dsp, *dp, ta;
	int		slen, devid_len, ascii, i, n, c, pre_alloc = FALSE;
	unsigned short	id_len, type;		/* for hibyte/lobyte */

	if (devidp != NULL)
		*devidp = NULL;
	if (minor_namep != NULL)
		*minor_namep = NULL;
	if (id != NULL)
		pre_alloc = TRUE;

	if (devidstr == NULL)
		return (DEVID_FAILURE);

	/* the string must atleast contain the ascii two byte header */
	slen = strlen(devidstr);
	if ((slen < 3) || (devidstr[0] != DEVID_MAGIC_MSB) ||
	    (devidstr[1] != DEVID_MAGIC_LSB))
		return (DEVID_FAILURE);

	/* "id0" is the encoded representation of a NULL device id */
	if ((devidstr[2] == '0') && (slen == 3))
		return (DEVID_SUCCESS);

	/* "id1,@S0" is the shortest possible, reject if shorter */
	if (slen <  7)
		return (DEVID_FAILURE);

	/* find the optional minor name, start after ',' */
	if ((msp = strchr(&devidstr[4], '/')) != NULL)
		msp++;

	/* skip devid processing if we are not asked to return it */
	if (devidp) {
		/* find the required '@' separator */
		if ((str = strchr(devidstr, '@')) == NULL)
			return (DEVID_FAILURE);
		str++;					/* skip '@' */

		/* pick up <type> after the '@' and verify */
		ta = *str++;
		ascii = DEVID_TYPE_ISASCII(ta);
		type = DEVID_TYPE_ASCIITOBIN(ta);
		if (type > DEVID_MAXTYPE)
			return (DEVID_FAILURE);

		/* determine length of id->did_id field */
		if (msp == NULL)
			id_len = strlen(str);
		else
			id_len = msp - str - 1;

		/* account for encoding: with hex, binary is half the size */
		if (!ascii) {
			/* hex id field must be even length */
			if (id_len & 1)
				return (DEVID_FAILURE);
			id_len /= 2;
		}

		/* add in size of the binary devid header */
		devid_len = id_len + sizeof (*id) - sizeof (id->did_id);

		/*
		 * Allocate space for devid if we are asked to decode it
		 * decode it and space wasn't pre-allocated.
		 */
		if (pre_alloc == FALSE) {
			if ((id = (impl_devid_t *)DEVID_MALLOC(
			    devid_len)) == NULL)
			return (DEVID_FAILURE);
		}

		/* decode header portion of the string into the binary devid */
		dsp = devidstr;
		id->did_magic_hi = *dsp++;		/* <magic> "id" */
		id->did_magic_lo = *dsp++;
		id->did_rev_hi = 0;
		id->did_rev_lo =
		    DEVID_REV_ASCIITOBIN(*dsp);		/* <rev> "1" */
		dsp++;					/* skip "1" */
		dsp++;					/* skip "," */
		for (i = 0; i < DEVID_HINT_SIZE; i++) {	/* <driver>@ */
			if (*dsp == '@')
				break;
			id->did_driver[i] = *dsp++;
		}
		for (; i < DEVID_HINT_SIZE; i++)
			id->did_driver[i] = 0;

		/* we must now be at the '@' */
		if (*dsp != '@')
			goto efree;

		/* set the type and length */
		DEVID_FORMTYPE(id, type);
		DEVID_FORMLEN(id, id_len);

		/* decode devid portion of string into the binary */
		for (i = 0, dsp = str, dp = &id->did_id[0];
		    i < id_len; i++, dp++) {
			if (ascii) {
				if (*dsp == '_')
					*dp = ' ';
				else if (*dsp == '~')
					*dp = 0x00;
				else
					*dp = *dsp;
				dsp++;
			} else {
				c = *dsp++;
				if (c >= '0' && c <= '9')
					n = (c - '0') & 0xFF;
				else if (c >= 'a' && c <= 'f')
					n = (c - ('a' - 10)) & 0xFF;
				else
					goto efree;
				n <<= 4;
				c = *dsp++;
				if (c >= '0' && c <= '9')
					n |= (c - '0') & 0xFF;
				else if (c >= 'a' && c <= 'f')
					n |= (c - ('a' - 10)) & 0xFF;
				else
					goto efree;
				*dp = n;
			}
		}

		/* verify result */
		if (DEVID_FUNC(devid_valid)((ddi_devid_t)id) != DEVID_RET_VALID)
			goto efree;
	}

	/* duplicate minor_name if we are asked to decode it */
	if (minor_namep && msp) {
		if ((*minor_namep = DEVID_MALLOC(strlen(msp) + 1)) == NULL)
			goto efree;
		(void) strcpy(*minor_namep, msp);
	}

	/* return pointer to binary */
	if (devidp)
		*devidp = (ddi_devid_t)id;
	return (DEVID_SUCCESS);

efree:
	if ((pre_alloc == FALSE) && (id))
		DEVID_FREE(id, devid_len);
	return (DEVID_FAILURE);
}


/*
 * Compare two device id's in string form
 *	-1 - id1 less than id2
 *	0  - equal
 * 	1  - id1 greater than id2
 */
int
#ifdef	_KERNEL
ddi_devid_str_compare(char *id1_str, char *id2_str)
#else	/* !_KERNEL */
devid_str_compare(char *id1_str, char *id2_str)
#endif	/* _KERNEL */
{
	int		rval	= DEVID_FAILURE;
	ddi_devid_t	devid1;
	ddi_devid_t	devid2;
#ifdef	_KERNEL
	/* kernel use static protected by lock. */
	static kmutex_t	id_lock;
	static uchar_t	id1[sizeof (impl_devid_t) + MAXPATHLEN];
	static uchar_t	id2[sizeof (impl_devid_t) + MAXPATHLEN];
#else	/* !_KERNEL */
	/* userland place on stack, since malloc might fail */
	uchar_t		id1[sizeof (impl_devid_t) + MAXPATHLEN];
	uchar_t		id2[sizeof (impl_devid_t) + MAXPATHLEN];
#endif	/* _KERNEL */

#ifdef	_KERNEL
	mutex_enter(&id_lock);
#endif	/* _KERNEL */

	/*
	 * encode string form of devid
	 */
	if ((devid_str_decode_id(id1_str, &devid1, NULL, (impl_devid_t *)id1) ==
	    DEVID_SUCCESS) &&
	    (devid_str_decode_id(id2_str, &devid2, NULL, (impl_devid_t *)id2) ==
	    DEVID_SUCCESS)) {
		rval = DEVID_FUNC(devid_compare)(devid1, devid2);
	}

#ifdef	_KERNEL
	mutex_exit(&id_lock);
#endif	/* _KERNEL */

	return (rval);
}