/* * 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. */ #include <sys/types.h> #include <sys/param.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/stream.h> #include <sys/strsubr.h> #include <sys/modctl.h> #include <sys/modhash.h> #include <sys/atomic.h> #include <sys/ddi.h> #include <sys/sunddi.h> #include <sys/t_lock.h> /* * This module provides the framework that manage STREAMS modules. * fmodsw_alloc() is called from modconf.c as a result of a module calling * mod_install() and fmodsw_free() is called as the result of the module * calling mod_remove(). * fmodsw_find() will find the fmodsw_impl_t structure relating to a named * module. There is no equivalent of driver major numbers for modules; the * the database of fmodsw_impl_t structures is purely keyed by name and * is hence a hash table to keep lookup cost to a minimum. */ /* * fmodsw_hash is the hash table that will be used to map module names to * their fmodsw_impl_t structures. The hash function requires that the value is * a power of 2 so this definition specifies the log of the hash table size. */ #define FMODSW_LOG_HASHSZ 8 /* * Hash table and associated reader-writer lock * * NOTE: Because the lock is global data, it is initialized to zero and hence * a call to rw_init() is not required. Similarly all the pointers in * the hash table will be implicitly initialized to NULL. */ #define FMODSW_HASHSZ (1 << FMODSW_LOG_HASHSZ) static fmodsw_impl_t *fmodsw_hash[FMODSW_HASHSZ]; static krwlock_t fmodsw_lock; /* * Debug code: * * This is not conditionally compiled since it may be useful to third * parties when developing new modules. */ #define BUFSZ 512 #define FMODSW_INIT 0x00000001 #define FMODSW_REGISTER 0x00000002 #define FMODSW_UNREGISTER 0x00000004 #define FMODSW_FIND 0x00000008 uint32_t fmodsw_debug_flags = 0x00000000; static void fmodsw_dprintf(uint_t flag, const char *fmt, ...) __KPRINTFLIKE(2); /* PRINTFLIKE2 */ static void i_fmodsw_dprintf(uint_t flag, const char *fmt, ...) { va_list alist; char buf[BUFSZ + 1]; char *ptr; if (fmodsw_debug_flags & flag) { va_start(alist, fmt); ptr = buf; (void) sprintf(ptr, "strmod debug: "); ptr += strlen(buf); (void) vsnprintf(ptr, buf + BUFSZ - ptr, fmt, alist); printf(buf); va_end(alist); } } /* * Local functions: */ #define FMODSW_HASH(_key) \ (uint_t)(((_key[0] << 4) | (_key[1] & 0x0f)) & (FMODSW_HASHSZ - 1)) #define FMODSW_KEYCMP(_k1, _k2, _match) \ { \ char *p1 = (char *)(_k1); \ char *p2 = (char *)(_k2); \ \ while (*p1 == *p2++) { \ if (*p1++ == '\0') { \ goto _match; \ } \ } \ } static int i_fmodsw_hash_insert(fmodsw_impl_t *fp) { uint_t bucket; fmodsw_impl_t **pp; fmodsw_impl_t *p; ASSERT(rw_write_held(&fmodsw_lock)); bucket = FMODSW_HASH(fp->f_name); for (pp = &(fmodsw_hash[bucket]); (p = *pp) != NULL; pp = &(p->f_next)) FMODSW_KEYCMP(p->f_name, fp->f_name, found); fp->f_next = p; *pp = fp; return (0); found: return (EEXIST); } static int i_fmodsw_hash_remove(const char *name, fmodsw_impl_t **fpp) { uint_t bucket; fmodsw_impl_t **pp; fmodsw_impl_t *p; ASSERT(rw_write_held(&fmodsw_lock)); bucket = FMODSW_HASH(name); for (pp = &(fmodsw_hash[bucket]); (p = *pp) != NULL; pp = &(p->f_next)) FMODSW_KEYCMP(p->f_name, name, found); return (ENOENT); found: if (p->f_ref > 0) return (EBUSY); *pp = p->f_next; *fpp = p; return (0); } static int i_fmodsw_hash_find(const char *name, fmodsw_impl_t **fpp) { uint_t bucket; fmodsw_impl_t *p; int rc = 0; ASSERT(rw_read_held(&fmodsw_lock)); bucket = FMODSW_HASH(name); for (p = fmodsw_hash[bucket]; p != NULL; p = p->f_next) FMODSW_KEYCMP(p->f_name, name, found); rc = ENOENT; found: *fpp = p; #ifdef DEBUG if (p != NULL) p->f_hits++; #endif /* DEBUG */ return (rc); } /* * Exported functions: */ int fmodsw_register(const char *name, struct streamtab *str, int flag) { fmodsw_impl_t *fp; int len; int err; uint_t qflag; uint_t sqtype; if ((len = strlen(name)) > FMNAMESZ) return (EINVAL); if ((fp = kmem_zalloc(sizeof (fmodsw_impl_t), KM_NOSLEEP)) == NULL) return (ENOMEM); (void) strncpy(fp->f_name, name, len); fp->f_name[len] = '\0'; if ((err = devflg_to_qflag(str, flag, &qflag, &sqtype)) != 0) goto failed; fp->f_str = str; fp->f_qflag = qflag; fp->f_sqtype = sqtype; if (qflag & (QPERMOD | QMTOUTPERIM)) fp->f_dmp = hold_dm(str, qflag, sqtype); rw_enter(&fmodsw_lock, RW_WRITER); if ((err = i_fmodsw_hash_insert(fp)) != 0) { rw_exit(&fmodsw_lock); goto failed; } rw_exit(&fmodsw_lock); i_fmodsw_dprintf(FMODSW_REGISTER, "registered module '%s'\n", name); return (0); failed: i_fmodsw_dprintf(FMODSW_REGISTER, "failed to register module '%s'\n", name); if (fp->f_dmp != NULL) rele_dm(fp->f_dmp); kmem_free(fp, sizeof (fmodsw_impl_t)); return (err); } int fmodsw_unregister(const char *name) { fmodsw_impl_t *fp; int err; rw_enter(&fmodsw_lock, RW_WRITER); if ((err = i_fmodsw_hash_remove(name, &fp)) != 0) { rw_exit(&fmodsw_lock); goto failed; } rw_exit(&fmodsw_lock); if (fp->f_dmp != NULL) rele_dm(fp->f_dmp); kmem_free(fp, sizeof (fmodsw_impl_t)); i_fmodsw_dprintf(FMODSW_UNREGISTER, "unregistered module '%s'\n", name); return (0); failed: i_fmodsw_dprintf(FMODSW_UNREGISTER, "failed to unregister module " "'%s'\n", name); return (err); } fmodsw_impl_t * fmodsw_find(const char *name, fmodsw_flags_t flags) { fmodsw_impl_t *fp; int id; try_again: rw_enter(&fmodsw_lock, RW_READER); if (i_fmodsw_hash_find(name, &fp) == 0) { if (flags & FMODSW_HOLD) { atomic_inc_32(&(fp->f_ref)); /* lock must be held */ ASSERT(fp->f_ref > 0); } rw_exit(&fmodsw_lock); return (fp); } rw_exit(&fmodsw_lock); if (flags & FMODSW_LOAD) { if ((id = modload("strmod", (char *)name)) != -1) { i_fmodsw_dprintf(FMODSW_FIND, "module '%s' loaded: id = %d\n", name, id); goto try_again; } } return (NULL); } void fmodsw_rele(fmodsw_impl_t *fp) { ASSERT(fp->f_ref > 0); atomic_dec_32(&(fp->f_ref)); }