root/usr/src/uts/common/io/sad_conf.c
/*
 * 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.
 */

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