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