/* * 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. */ /* Copyright (c) 1988 AT&T */ /* All Rights Reserved */ /*- * Copyright (c) 2010 Pawel Jakub Dawidek * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include __strong_alias(ddi_strtol,ddi_strtoul) /* * String to integer conversion routines. * * This file is derived from usr/src/common/util/strtol.c * * We cannot use the user land versions as there is no errno to report * error in kernel. So the return value is used to return an error, * and the result is stored in an extra parameter passed by reference. * Otherwise, the following functions are identical to the user land * versions. */ /* * We should have a kernel version of ctype.h. */ #define isalnum(ch) (isalpha(ch) || isdigit(ch)) #define isalpha(ch) (isupper(ch) || islower(ch)) #define isdigit(ch) ((ch) >= '0' && (ch) <= '9') #define islower(ch) ((ch) >= 'a' && (ch) <= 'z') #define isspace(ch) (((ch) == ' ') || ((ch) == '\r') || ((ch) == '\n') || \ ((ch) == '\t') || ((ch) == '\f')) #define isupper(ch) ((ch) >= 'A' && (ch) <= 'Z') #define isxdigit(ch) (isdigit(ch) || ((ch) >= 'a' && (ch) <= 'f') || \ ((ch) >= 'A' && (ch) <= 'F')) #define DIGIT(x) \ (isdigit(x) ? (x) - '0' : islower(x) ? (x) + 10 - 'a' : (x) + 10 - 'A') #define MBASE ('z' - 'a' + 1 + 10) /* * The following macro is a local version of isalnum() which limits * alphabetic characters to the ranges a-z and A-Z; locale dependent * characters will not return 1. The members of a-z and A-Z are * assumed to be in ascending order and contiguous */ #define lisalnum(x) \ (isdigit(x) || ((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z')) static int do_mkdirp(const char *path) { struct lwp *l = curlwp; int mode; int error; register_t ret; const char *s, *e; char *here; error = 0; mode = 493; if (*path != '/') panic("Not an absolute path"); here = PNBUF_GET(); for (s = path;; s = e) { e = strchr(s + 1, '/'); if (e == NULL) break; strlcpy(here, path, e - path + 1); error = do_sys_mkdir((const char *)here, mode, UIO_SYSSPACE); } PNBUF_PUT(here); if (error == EEXIST) error = 0; return error; } static void do_rmdirp(const char *path) { struct pathbuf *pb; struct nameidata nd; char *here, *e; int error; here = PNBUF_GET(); strlcpy(here, path, MAXPATHLEN); while ((e = strrchr(here, '/')) && e != here) { *e = '\0'; pb = pathbuf_create(here); if (pb == NULL) break; /* XXX need do_sys_rmdir()? */ NDINIT(&nd, DELETE, LOCKPARENT | LOCKLEAF | TRYEMULROOT, pb); error = namei(&nd); if (error) { pathbuf_destroy(pb); break; } if ((nd.ni_vp->v_vflag & VV_ROOT) || nd.ni_vp->v_type != VDIR || nd.ni_vp->v_mountedhere || nd.ni_vp == nd.ni_dvp) { VOP_ABORTOP(nd.ni_dvp, &nd.ni_cnd); if (nd.ni_vp == nd.ni_dvp) vrele(nd.ni_dvp); else vput(nd.ni_dvp); vput(nd.ni_vp); pathbuf_destroy(pb); break; } error = VOP_RMDIR(nd.ni_dvp, nd.ni_vp, &nd.ni_cnd); vput(nd.ni_dvp); pathbuf_destroy(pb); if (error) break; } PNBUF_PUT(here); } int ddi_strtoul(const char *str, char **nptr, int base, unsigned long *result) { unsigned long val; int c; int xx; unsigned long multmax; int neg = 0; const char **ptr = (const char **)nptr; const unsigned char *ustr = (const unsigned char *)str; if (ptr != (const char **)0) *ptr = (char *)ustr; /* in case no number is formed */ if (base < 0 || base > MBASE || base == 1) { /* base is invalid -- should be a fatal error */ return (EINVAL); } if (!isalnum(c = *ustr)) { while (isspace(c)) c = *++ustr; switch (c) { case '-': neg++; /* FALLTHROUGH */ case '+': c = *++ustr; } } if (base == 0) if (c != '0') base = 10; else if (ustr[1] == 'x' || ustr[1] == 'X') base = 16; else base = 8; /* * for any base > 10, the digits incrementally following * 9 are assumed to be "abc...z" or "ABC...Z" */ if (!lisalnum(c) || (xx = DIGIT(c)) >= base) return (EINVAL); /* no number formed */ if (base == 16 && c == '0' && (ustr[1] == 'x' || ustr[1] == 'X') && isxdigit(ustr[2])) c = *(ustr += 2); /* skip over leading "0x" or "0X" */ multmax = ULONG_MAX / (unsigned long)base; val = DIGIT(c); for (c = *++ustr; lisalnum(c) && (xx = DIGIT(c)) < base; ) { if (val > multmax) goto overflow; val *= base; if (ULONG_MAX - val < xx) goto overflow; val += xx; c = *++ustr; } if (ptr != (const char **)0) *ptr = (char *)ustr; *result = neg ? -val : val; return (0); overflow: for (c = *++ustr; lisalnum(c) && (xx = DIGIT(c)) < base; (c = *++ustr)) ; if (ptr != (const char **)0) *ptr = (char *)ustr; return (ERANGE); } int ddi_strtoull(const char *str, char **nptr, int base, unsigned long long *result) { *result = (unsigned long long)strtoull(str, nptr, base); if (*result == 0) return (EINVAL); else if (*result == ULLONG_MAX) return (ERANGE); return (0); } /* * Find first bit set in a mask (returned counting from 1 up) */ int ddi_ffs(long mask) { return (ffs(mask)); } /* * Find last bit set. Take mask and clear * all but the most significant bit, and * then let ffs do the rest of the work. * * Algorithm courtesy of Steve Chessin. */ int ddi_fls(long mask) { while (mask) { long nx; if ((nx = (mask & (mask - 1))) == 0) break; mask = nx; } return (ffs(mask)); } /* * The next five routines comprise generic storage management utilities * for driver soft state structures (in "the old days," this was done * with a statically sized array - big systems and dynamic loading * and unloading make heap allocation more attractive) */ /* * Allocate a set of pointers to 'n_items' objects of size 'size' * bytes. Each pointer is initialized to nil. * * The 'size' and 'n_items' values are stashed in the opaque * handle returned to the caller. * * This implementation interprets 'set of pointers' to mean 'array * of pointers' but note that nothing in the interface definition * precludes an implementation that uses, for example, a linked list. * However there should be a small efficiency gain from using an array * at lookup time. * * NOTE As an optimization, we make our growable array allocations in * powers of two (bytes), since that's how much kmem_alloc (currently) * gives us anyway. It should save us some free/realloc's .. * * As a further optimization, we make the growable array start out * with MIN_N_ITEMS in it. */ /* * This data structure is entirely private to the soft state allocator. */ struct i_ddi_soft_state { void **array; /* the array of pointers */ kmutex_t lock; /* serialize access to this struct */ size_t size; /* how many bytes per state struct */ size_t n_items; /* how many structs herein */ struct i_ddi_soft_state *next; /* 'dirty' elements */ }; #define MIN_N_ITEMS 8 /* 8 void *'s == 32 bytes */ int ddi_soft_state_init(void **state_p, size_t size, size_t n_items) { struct i_ddi_soft_state *ss; if (state_p == NULL || *state_p != NULL || size == 0) return (EINVAL); ss = kmem_zalloc(sizeof (*ss), KM_SLEEP); mutex_init(&ss->lock, NULL, MUTEX_DRIVER, NULL); ss->size = size; if (n_items < MIN_N_ITEMS) ss->n_items = MIN_N_ITEMS; else { int bitlog; if ((bitlog = ddi_fls(n_items)) == ddi_ffs(n_items)) bitlog--; ss->n_items = 1 << bitlog; } ASSERT(ss->n_items >= n_items); ss->array = kmem_zalloc(ss->n_items * sizeof (void *), KM_SLEEP); *state_p = ss; return (0); } /* * Allocate a state structure of size 'size' to be associated * with item 'item'. * * In this implementation, the array is extended to * allow the requested offset, if needed. */ int ddi_soft_state_zalloc(void *state, int item) { struct i_ddi_soft_state *ss; void **array; void *new_element; if ((ss = state) == NULL || item < 0) return (DDI_FAILURE); mutex_enter(&ss->lock); if (ss->size == 0) { mutex_exit(&ss->lock); cmn_err(CE_WARN, "ddi_soft_state_zalloc: bad handle"); return (DDI_FAILURE); } array = ss->array; /* NULL if ss->n_items == 0 */ ASSERT(ss->n_items != 0 && array != NULL); /* * refuse to tread on an existing element */ if (item < ss->n_items && array[item] != NULL) { mutex_exit(&ss->lock); return (DDI_FAILURE); } /* * Allocate a new element to plug in */ new_element = kmem_zalloc(ss->size, KM_SLEEP); /* * Check if the array is big enough, if not, grow it. */ if (item >= ss->n_items) { void **new_array; size_t new_n_items; struct i_ddi_soft_state *dirty; /* * Allocate a new array of the right length, copy * all the old pointers to the new array, then * if it exists at all, put the old array on the * dirty list. * * Note that we can't kmem_free() the old array. * * Why -- well the 'get' operation is 'mutex-free', so we * can't easily catch a suspended thread that is just about * to dereference the array we just grew out of. So we * cons up a header and put it on a list of 'dirty' * pointer arrays. (Dirty in the sense that there may * be suspended threads somewhere that are in the middle * of referencing them). Fortunately, we -can- garbage * collect it all at ddi_soft_state_fini time. */ new_n_items = ss->n_items; while (new_n_items < (1 + item)) new_n_items <<= 1; /* double array size .. */ ASSERT(new_n_items >= (1 + item)); /* sanity check! */ new_array = kmem_zalloc(new_n_items * sizeof (void *), KM_SLEEP); /* * Copy the pointers into the new array */ bcopy(array, new_array, ss->n_items * sizeof (void *)); /* * Save the old array on the dirty list */ dirty = kmem_zalloc(sizeof (*dirty), KM_SLEEP); dirty->array = ss->array; dirty->n_items = ss->n_items; dirty->next = ss->next; ss->next = dirty; ss->array = (array = new_array); ss->n_items = new_n_items; } ASSERT(array != NULL && item < ss->n_items && array[item] == NULL); array[item] = new_element; mutex_exit(&ss->lock); return (DDI_SUCCESS); } /* * Fetch a pointer to the allocated soft state structure. * * This is designed to be cheap. * * There's an argument that there should be more checking for * nil pointers and out of bounds on the array.. but we do a lot * of that in the alloc/free routines. * * An array has the convenience that we don't need to lock read-access * to it c.f. a linked list. However our "expanding array" strategy * means that we should hold a readers lock on the i_ddi_soft_state * structure. * * However, from a performance viewpoint, we need to do it without * any locks at all -- this also makes it a leaf routine. The algorithm * is 'lock-free' because we only discard the pointer arrays at * ddi_soft_state_fini() time. */ void * ddi_get_soft_state(void *state, int item) { struct i_ddi_soft_state *ss = state; ASSERT(ss != NULL && item >= 0); if (item < ss->n_items && ss->array != NULL) return (ss->array[item]); return (NULL); } /* * Free the state structure corresponding to 'item.' Freeing an * element that has either gone or was never allocated is not * considered an error. Note that we free the state structure, but * we don't shrink our pointer array, or discard 'dirty' arrays, * since even a few pointers don't really waste too much memory. * * Passing an item number that is out of bounds, or a null pointer will * provoke an error message. */ void ddi_soft_state_free(void *state, int item) { struct i_ddi_soft_state *ss; void **array; void *element; static char msg[] = "ddi_soft_state_free:"; if ((ss = state) == NULL) { cmn_err(CE_WARN, "%s null handle", msg); return; } element = NULL; mutex_enter(&ss->lock); if ((array = ss->array) == NULL || ss->size == 0) { cmn_err(CE_WARN, "%s bad handle", msg); } else if (item < 0 || item >= ss->n_items) { cmn_err(CE_WARN, "%s item %d not in range [0..%lu]", msg, item, ss->n_items - 1); } else if (array[item] != NULL) { element = array[item]; array[item] = NULL; } mutex_exit(&ss->lock); if (element) kmem_free(element, ss->size); } /* * Free the entire set of pointers, and any * soft state structures contained therein. * * Note that we don't grab the ss->lock mutex, even though * we're inspecting the various fields of the data structure. * * There is an implicit assumption that this routine will * never run concurrently with any of the above on this * particular state structure i.e. by the time the driver * calls this routine, there should be no other threads * running in the driver. */ void ddi_soft_state_fini(void **state_p) { struct i_ddi_soft_state *ss, *dirty; int item; static char msg[] = "ddi_soft_state_fini:"; if (state_p == NULL || (ss = *state_p) == NULL) { cmn_err(CE_WARN, "%s null handle", msg); return; } if (ss->size == 0) { cmn_err(CE_WARN, "%s bad handle", msg); return; } if (ss->n_items > 0) { for (item = 0; item < ss->n_items; item++) ddi_soft_state_free(ss, item); kmem_free(ss->array, ss->n_items * sizeof (void *)); } /* * Now delete any dirty arrays from previous 'grow' operations */ for (dirty = ss->next; dirty; dirty = ss->next) { ss->next = dirty->next; kmem_free(dirty->array, dirty->n_items * sizeof (void *)); kmem_free(dirty, sizeof (*dirty)); } mutex_destroy(&ss->lock); kmem_free(ss, sizeof (*ss)); *state_p = NULL; } int ddi_create_minor_node(dev_info_t *dip, char *name, int spec_type, minor_t minor_num, char *node_type, int flag) { struct lwp *l = curlwp; vnode_t *vp; enum vtype vtype; struct stat sb; char *pn; dev_t dev; int error; pn = PNBUF_GET(); if (spec_type == S_IFCHR) { vtype = VCHR; dev = makedev(dip->di_cmajor, minor_num); snprintf(pn, MAXPATHLEN, "/dev/zvol/rdsk/%s", name); } else if (spec_type == S_IFBLK) { vtype = VBLK; dev = makedev(dip->di_bmajor, minor_num); snprintf(pn, MAXPATHLEN, "/dev/zvol/dsk/%s", name); } else { panic("bad spectype %#x", spec_type); } spec_type |= (S_IRUSR | S_IWUSR); /* Create missing directories. */ if ((error = do_mkdirp(pn)) != 0) goto exit; /* * If node exists and has correct type and rdev all done, * otherwise unlink the node. */ if (namei_simple_kernel(pn, NSM_NOFOLLOW_NOEMULROOT, &vp) == 0) { vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); error = vn_stat(vp, &sb); VOP_UNLOCK(vp, 0); if (error == 0 && vp->v_type == vtype && sb.st_rdev == dev) { vrele(vp); return 0; } vrele(vp); (void)do_sys_unlink(pn, UIO_SYSSPACE); } error = do_sys_mknod(l, pn, spec_type, dev, UIO_SYSSPACE); exit: PNBUF_PUT(pn); return error; } void ddi_remove_minor_node(dev_info_t *dip, char *name) { char *pn; /* Unlink block device and remove empty directories. */ pn = PNBUF_GET(); snprintf(pn, MAXPATHLEN, "/dev/zvol/dsk/%s", name); (void)do_sys_unlink(pn, UIO_SYSSPACE); do_rmdirp(pn); PNBUF_PUT(pn); /* Unlink raw device and remove empty directories. */ pn = PNBUF_GET(); snprintf(pn, MAXPATHLEN, "/dev/zvol/rdsk/%s", name); (void)do_sys_unlink(pn, UIO_SYSSPACE); do_rmdirp(pn); PNBUF_PUT(pn); } #if 0 clock_t ddi_get_lbolt() { return hardclock_ticks; } int64_t ddi_get_lbolt64() { return hardclock_ticks; } #endif