/*
* 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 <pjd@FreeBSD.org>
* 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 <sys/types.h>
#include <sys/sunddi.h>
#include <sys/debug.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/lwp.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/namei.h>
#include <sys/stat.h>
#include <sys/vnode.h>
#include <sys/vfs_syscalls.h>
__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