root/usr/src/uts/common/io/sock_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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/sysmacros.h>
#include <sys/atomic.h>
#include <sys/strsubr.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/cmn_err.h>
#include <sys/modctl.h>
#include <sys/sdt.h>

list_t smod_list;
kmutex_t smod_list_lock;

so_create_func_t sock_comm_create_function;
so_destroy_func_t sock_comm_destroy_function;

static smod_info_t *smod_create(const char *);
static void smod_destroy(smod_info_t *);

extern void smod_add(smod_info_t *);

void
smod_init(void)
{
        list_create(&smod_list, sizeof (smod_info_t),
            offsetof(smod_info_t, smod_node));
        mutex_init(&smod_list_lock, NULL, MUTEX_DEFAULT, NULL);
}

static smod_info_t *
smod_find(const char *modname)
{
        smod_info_t *smodp;

        ASSERT(MUTEX_HELD(&smod_list_lock));

        for (smodp = list_head(&smod_list); smodp != NULL;
            smodp = list_next(&smod_list, smodp))
                if (strcmp(smodp->smod_name, modname) == 0)
                        return (smodp);
        return (NULL);
}

/*
 * Register the socket module.
 */
int
smod_register(const smod_reg_t *reg)
{
        smod_info_t     *smodp;

        /*
         * Make sure the socket module does not depend on capabilities
         * not available on the system.
         */
        if (reg->smod_version != SOCKMOD_VERSION ||
            reg->smod_dc_version != SOCK_DC_VERSION ||
            reg->smod_uc_version != SOCK_UC_VERSION) {
                cmn_err(CE_WARN,
                    "Failed to register socket module %s: version mismatch",
                    reg->smod_name);
                return (EINVAL);
        }

#ifdef DEBUG
        mutex_enter(&smod_list_lock);
        if ((smodp = smod_find(reg->smod_name)) != NULL) {
                mutex_exit(&smod_list_lock);
                return (EEXIST);
        }
        mutex_exit(&smod_list_lock);
#endif

        smodp = smod_create(reg->smod_name);
        smodp->smod_version = reg->smod_version;
        if (strcmp(smodp->smod_name, SOTPI_SMOD_NAME) == 0 ||
            strcmp(smodp->smod_name, "socksctp") == 0 ||
            strcmp(smodp->smod_name, "socksdp") == 0) {
                ASSERT(smodp->smod_proto_create_func == NULL);
                ASSERT(reg->__smod_priv != NULL);
                smodp->smod_sock_create_func =
                    reg->__smod_priv->smodp_sock_create_func;
                smodp->smod_sock_destroy_func =
                    reg->__smod_priv->smodp_sock_destroy_func;
                smodp->smod_proto_create_func = NULL;
        } else {
                if (reg->smod_proto_create_func == NULL ||
                    (reg->__smod_priv != NULL &&
                    (reg->__smod_priv->smodp_sock_create_func != NULL ||
                    reg->__smod_priv->smodp_sock_destroy_func != NULL))) {
#ifdef DEBUG
                        cmn_err(CE_CONT, "smod_register of %s failed",
                            smodp->smod_name);
#endif
                        smod_destroy(smodp);
                        return (EINVAL);
                }
                smodp->smod_proto_create_func = reg->smod_proto_create_func;
                smodp->smod_sock_create_func = sock_comm_create_function;
                smodp->smod_sock_destroy_func = sock_comm_destroy_function;
                smodp->smod_uc_version = reg->smod_uc_version;
                smodp->smod_dc_version = reg->smod_dc_version;
                if (reg->__smod_priv != NULL) {
                        smodp->smod_proto_fallback_func =
                            reg->__smod_priv->smodp_proto_fallback_func;
                        smodp->smod_fallback_devpath_v4 =
                            reg->__smod_priv->smodp_fallback_devpath_v4;
                        smodp->smod_fallback_devpath_v6 =
                            reg->__smod_priv->smodp_fallback_devpath_v6;
                }
        }
        smod_add(smodp);
        return (0);
}

/*
 * Unregister the socket module
 */
int
smod_unregister(const char *mod_name)
{
        smod_info_t     *smodp;

        mutex_enter(&smod_list_lock);
        if ((smodp = smod_find(mod_name)) != NULL) {
                if (smodp->smod_refcnt != 0) {
                        mutex_exit(&smod_list_lock);
                        return (EBUSY);
                } else {
                        /*
                         * Delete the entry from the socket module list.
                         */
                        list_remove(&smod_list, smodp);
                        mutex_exit(&smod_list_lock);

                        smod_destroy(smodp);
                        return (0);
                }
        }
        mutex_exit(&smod_list_lock);

        return (ENXIO);
}

/*
 * Initialize the socket module entry.
 */
static smod_info_t *
smod_create(const char *modname)
{
        smod_info_t *smodp;
        int len;

        smodp = kmem_zalloc(sizeof (*smodp), KM_SLEEP);
        len = strlen(modname) + 1;
        smodp->smod_name = kmem_alloc(len, KM_SLEEP);
        bcopy(modname, smodp->smod_name, len);
        smodp->smod_name[len - 1] = '\0';
        return (smodp);
}

/*
 * Clean up the socket module part of the sockparams entry.
 */
static void
smod_destroy(smod_info_t *smodp)
{
        ASSERT(smodp->smod_name != NULL);
        ASSERT(smodp->smod_refcnt == 0);
        ASSERT(!list_link_active(&smodp->smod_node));
        ASSERT(strcmp(smodp->smod_name, "socktpi") != 0);

        kmem_free(smodp->smod_name, strlen(smodp->smod_name) + 1);
        smodp->smod_name = NULL;
        smodp->smod_proto_create_func = NULL;
        smodp->smod_sock_create_func = NULL;
        smodp->smod_sock_destroy_func = NULL;
        kmem_free(smodp, sizeof (*smodp));
}

/*
 * Add an entry at the front of the socket module list.
 */
void
smod_add(smod_info_t *smodp)
{
        ASSERT(smodp != NULL);
        mutex_enter(&smod_list_lock);
        list_insert_head(&smod_list, smodp);
        mutex_exit(&smod_list_lock);
}

/*
 * Lookup the socket module table by the socket module name.
 * If there is an existing entry, then increase the reference count.
 * Otherwise we load the module and in the module register function create
 * a new entry and add it to the end of the socket module table.
 */
smod_info_t *
smod_lookup_byname(const char *modname)
{
        smod_info_t *smodp;
        int error;

again:
        /*
         * If find an entry, increase the reference count and
         * return the entry pointer.
         */
        mutex_enter(&smod_list_lock);
        if ((smodp = smod_find(modname)) != NULL) {
                SMOD_INC_REF(smodp);
                mutex_exit(&smod_list_lock);
                return (smodp);
        }
        mutex_exit(&smod_list_lock);

        /*
         * We have a sockmod, and it is not loaded.
         * Load the module into the kernel, modload() will
         * take care of the multiple threads.
         */
        DTRACE_PROBE1(load__socket__module, char *, modname);
        error = modload(SOCKMOD_PATH, modname);
        if (error == -1) {
                cmn_err(CE_CONT, "modload of %s/%s failed",
                    SOCKMOD_PATH, modname);
                return (NULL);
        }
        goto again;
}