sad_conf.c revision 2ea701aa039ac7c64509e62bb4a333fb79e9b069
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Config dependent data structures for the Streams Administrative Driver
* (or "Ballad of the SAD Cafe").
*/
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/stream.h>
#include <sys/strsubr.h>
#include <sys/sad.h>
#include <sys/kmem.h>
#include <sys/sysmacros.h>
/*
* Currently we store all the sad data in a hash table keyed by major
* number. This is far from ideal. It means that if a single device
* starts using lots of SAP_ONE entries all its entries will hash
* to the same bucket and we'll get very long chains for that bucket.
*
* Unfortunately, it's not possible to hash by a different key or to easily
* break up our one hash into seperate hashs. The reason is because
* the hash contains mixed data types. Ie, it has three different
* types of autopush nodes in it: SAP_ALL, SAP_RANGE, SAP_ONE. Not
* only does the hash table contain nodes of different types, but we
* have to be able to search the table with a node of one type that
* might match another node with a different type. (ie, we might search
* for a SAP_ONE node with a value that matches a SAP_ALL node in the
* hash, or vice versa.)
*
* An ideal solution would probably be an AVL tree sorted by major
* numbers. Each node in the AVL tree would have the following optional
* data associated with it:
* - a single SAP_ALL autopush node
* - an or avl tree or hash table of SAP_RANGE and SAP_ONE autopush
* nodes indexed by minor numbers. perhaps two separate tables,
* one for each type of autopush nodes.
*
* Note that regardless of how the data is stored there can't be any overlap
* stored between autopush nodes. For example, if there is a SAP_ALL node
* for a given major number then there can't be any SAP_RANGE or SAP_ONE
* nodes for that same major number.
*/
/*
* Private Internal Interfaces
*/
/*ARGSUSED*/
static uint_t
sad_hash_alg(void *hash_data, mod_hash_key_t key)
{
struct apcommon *apc = (struct apcommon *)key;
ASSERT(sad_apc_verify(apc) == 0);
return (apc->apc_major);
}
/*
* Compare hash keys based off of major, minor, lastminor, and type.
*/
static int
sad_hash_keycmp(mod_hash_key_t key1, mod_hash_key_t key2)
{
struct apcommon *apc1 = (struct apcommon *)key1;
struct apcommon *apc2 = (struct apcommon *)key2;
ASSERT(sad_apc_verify(apc1) == 0);
ASSERT(sad_apc_verify(apc2) == 0);
/* Filter out cases where the major number doesn't match. */
if (apc1->apc_major != apc2->apc_major)
return (1);
/* If either type is SAP_ALL then we're done. */
if ((apc1->apc_cmd == SAP_ALL) || (apc2->apc_cmd == SAP_ALL))
return (0);
/* Deal with the case where both types are SAP_ONE. */
if ((apc1->apc_cmd == SAP_ONE) && (apc2->apc_cmd == SAP_ONE)) {
/* Check if the minor numbers match. */
return (apc1->apc_minor != apc2->apc_minor);
}
/* Deal with the case where both types are SAP_RANGE. */
if ((apc1->apc_cmd == SAP_RANGE) && (apc2->apc_cmd == SAP_RANGE)) {
/* Check for overlapping ranges. */
if ((apc1->apc_lastminor < apc2->apc_minor) ||
(apc1->apc_minor > apc2->apc_lastminor))
return (1);
return (0);
}
/*
* We know that one type is SAP_ONE and the other is SAP_RANGE.
* So now let's do range matching.
*/
if (apc1->apc_cmd == SAP_RANGE) {
ASSERT(apc2->apc_cmd == SAP_ONE);
if ((apc1->apc_lastminor < apc2->apc_minor) ||
(apc1->apc_minor > apc2->apc_minor))
return (1);
} else {
ASSERT(apc1->apc_cmd == SAP_ONE);
ASSERT(apc2->apc_cmd == SAP_RANGE);
if ((apc1->apc_minor < apc2->apc_minor) ||
(apc1->apc_minor > apc2->apc_lastminor))
return (1);
}
return (0);
}
/*ARGSUSED*/
static uint_t
sad_hash_free_value(mod_hash_key_t key, mod_hash_val_t *val, void *arg)
{
struct autopush *ap = (struct autopush *)val;
ASSERT(ap->ap_cnt > 0);
if (--(ap->ap_cnt) == 0)
kmem_free(ap, sizeof (struct autopush));
return (MH_WALK_CONTINUE);
}
/*
* External Interfaces
*/
int
sad_apc_verify(struct apcommon *apc)
{
/* sanity check the number of modules to push */
if ((apc->apc_npush == 0) || (apc->apc_npush > MAXAPUSH) ||
(apc->apc_npush > nstrpush))
return (EINVAL);
/* Check for NODEV major vaule */
if (apc->apc_major == -1)
return (EINVAL);
switch (apc->apc_cmd) {
case SAP_ALL:
case SAP_ONE:
/*
* Really, we'd like to be strict here and make sure that
* apc_lastminor is 0 (since setting apc_lastminor for
* SAP_ALL and SAP_ONE commands doesn't make any sense),
* but we can't since historically apc_lastminor has been
* silently ignored for non-SAP_RANGE commands.
*/
break;
case SAP_RANGE:
if (apc->apc_lastminor <= apc->apc_minor)
return (ERANGE);
break;
default:
return (EINVAL);
}
return (0);
}
int
sad_ap_verify(struct autopush *ap)
{
int ret, i;
if ((ret = sad_apc_verify(&ap->ap_common)) != 0)
return (ret);
/*
* Validate that the specified list of modules exist. Note that
* ap_npush has already been sanity checked by sad_apc_verify().
*/
for (i = 0; i < ap->ap_npush; i++) {
ap->ap_list[i][FMNAMESZ] = '\0';
if (fmodsw_find(ap->ap_list[i], FMODSW_LOAD) == NULL)
return (EINVAL);
}
return (0);
}
struct autopush *
sad_ap_alloc(void)
{
struct autopush *ap_new;
ap_new = kmem_zalloc(sizeof (struct autopush), KM_SLEEP);
ap_new->ap_cnt = 1;
return (ap_new);
}
void
sad_ap_rele(struct autopush *ap, str_stack_t *ss)
{
mutex_enter(&ss->ss_sad_lock);
ASSERT(ap->ap_cnt > 0);
if (--(ap->ap_cnt) == 0) {
mutex_exit(&ss->ss_sad_lock);
kmem_free(ap, sizeof (struct autopush));
} else {
mutex_exit(&ss->ss_sad_lock);
}
}
void
sad_ap_insert(struct autopush *ap, str_stack_t *ss)
{
ASSERT(MUTEX_HELD(&ss->ss_sad_lock));
ASSERT(sad_apc_verify(&ap->ap_common) == 0);
ASSERT(sad_ap_find(&ap->ap_common, ss) == NULL);
(void) mod_hash_insert(ss->ss_sad_hash, &ap->ap_common, ap);
}
void
sad_ap_remove(struct autopush *ap, str_stack_t *ss)
{
struct autopush *ap_removed = NULL;
ASSERT(MUTEX_HELD(&ss->ss_sad_lock));
(void) mod_hash_remove(ss->ss_sad_hash, &ap->ap_common,
(mod_hash_val_t *)&ap_removed);
ASSERT(ap == ap_removed);
}
struct autopush *
sad_ap_find(struct apcommon *apc, str_stack_t *ss)
{
struct autopush *ap_result = NULL;
ASSERT(MUTEX_HELD(&ss->ss_sad_lock));
ASSERT(sad_apc_verify(apc) == 0);
(void) mod_hash_find(ss->ss_sad_hash, apc,
(mod_hash_val_t *)&ap_result);
if (ap_result != NULL)
ap_result->ap_cnt++;
return (ap_result);
}
struct autopush *
sad_ap_find_by_dev(dev_t dev, str_stack_t *ss)
{
struct apcommon apc;
struct autopush *ap_result;
ASSERT(MUTEX_NOT_HELD(&ss->ss_sad_lock));
/* prepare an apcommon structure to search with */
apc.apc_cmd = SAP_ONE;
apc.apc_major = getmajor(dev);
apc.apc_minor = getminor(dev);
/*
* the following values must be set but initialized to have a
* valid apcommon struct, but since we're only using this
* structure to do a query the values are never actually used.
*/
apc.apc_npush = 1;
apc.apc_lastminor = 0;
mutex_enter(&ss->ss_sad_lock);
ap_result = sad_ap_find(&apc, ss);
mutex_exit(&ss->ss_sad_lock);
return (ap_result);
}
void
sad_initspace(str_stack_t *ss)
{
mutex_init(&ss->ss_sad_lock, NULL, MUTEX_DEFAULT, NULL);
ss->ss_sad_hash_nchains = 127;
ss->ss_sadcnt = 16;
ss->ss_saddev = kmem_zalloc(ss->ss_sadcnt * sizeof (struct saddev),
KM_SLEEP);
ss->ss_sad_hash = mod_hash_create_extended("sad_hash",
ss->ss_sad_hash_nchains, mod_hash_null_keydtor,
mod_hash_null_valdtor,
sad_hash_alg, NULL, sad_hash_keycmp, KM_SLEEP);
}
void
sad_freespace(str_stack_t *ss)
{
kmem_free(ss->ss_saddev, ss->ss_sadcnt * sizeof (struct saddev));
ss->ss_saddev = NULL;
mutex_enter(&ss->ss_sad_lock);
mod_hash_walk(ss->ss_sad_hash, sad_hash_free_value, NULL);
mod_hash_destroy_hash(ss->ss_sad_hash);
ss->ss_sad_hash = NULL;
mutex_exit(&ss->ss_sad_lock);
mutex_destroy(&ss->ss_sad_lock);
}