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