stevel@tonic-gate | 7c478bd | 2005-06-14 00:00:00 -0700 | [diff] [blame] | 1 | /* |
| 2 | * CDDL HEADER START |
| 3 | * |
| 4 | * The contents of this file are subject to the terms of the |
| 5 | * Common Development and Distribution License, Version 1.0 only |
| 6 | * (the "License"). You may not use this file except in compliance |
| 7 | * with the License. |
| 8 | * |
| 9 | * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| 10 | * or http://www.opensolaris.org/os/licensing. |
| 11 | * See the License for the specific language governing permissions |
| 12 | * and limitations under the License. |
| 13 | * |
| 14 | * When distributing Covered Code, include this CDDL HEADER in each |
| 15 | * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| 16 | * If applicable, add the following below this CDDL HEADER, with the |
| 17 | * fields enclosed by brackets "[]" replaced with your own identifying |
| 18 | * information: Portions Copyright [yyyy] [name of copyright owner] |
| 19 | * |
| 20 | * CDDL HEADER END |
| 21 | */ |
| 22 | /* |
| 23 | * Copyright 2004 Sun Microsystems, Inc. All rights reserved. |
| 24 | * Use is subject to license terms. |
| 25 | */ |
| 26 | |
stevel@tonic-gate | 7c478bd | 2005-06-14 00:00:00 -0700 | [diff] [blame] | 27 | #include <sys/types.h> |
| 28 | #include <sys/param.h> |
| 29 | #include <sys/systm.h> |
| 30 | #include <sys/conf.h> |
| 31 | #include <sys/stream.h> |
| 32 | #include <sys/strsubr.h> |
| 33 | #include <sys/modctl.h> |
| 34 | #include <sys/modhash.h> |
| 35 | #include <sys/atomic.h> |
| 36 | |
| 37 | #include <sys/ddi.h> |
| 38 | #include <sys/sunddi.h> |
| 39 | #include <sys/t_lock.h> |
| 40 | |
| 41 | /* |
| 42 | * This module provides the framework that manage STREAMS modules. |
| 43 | * fmodsw_alloc() is called from modconf.c as a result of a module calling |
| 44 | * mod_install() and fmodsw_free() is called as the result of the module |
| 45 | * calling mod_remove(). |
| 46 | * fmodsw_find() will find the fmodsw_impl_t structure relating to a named |
| 47 | * module. There is no equivalent of driver major numbers for modules; the |
| 48 | * the database of fmodsw_impl_t structures is purely keyed by name and |
| 49 | * is hence a hash table to keep lookup cost to a minimum. |
| 50 | */ |
| 51 | |
| 52 | /* |
| 53 | * fmodsw_hash is the hash table that will be used to map module names to |
| 54 | * their fmodsw_impl_t structures. The hash function requires that the value is |
| 55 | * a power of 2 so this definition specifies the log of the hash table size. |
| 56 | */ |
| 57 | #define FMODSW_LOG_HASHSZ 8 |
| 58 | |
| 59 | /* |
| 60 | * Hash table and associated reader-writer lock |
| 61 | * |
| 62 | * NOTE: Because the lock is global data, it is initialized to zero and hence |
| 63 | * a call to rw_init() is not required. Similarly all the pointers in |
| 64 | * the hash table will be implicitly initialized to NULL. |
| 65 | */ |
| 66 | #define FMODSW_HASHSZ (1 << FMODSW_LOG_HASHSZ) |
| 67 | |
| 68 | static fmodsw_impl_t *fmodsw_hash[FMODSW_HASHSZ]; |
| 69 | static krwlock_t fmodsw_lock; |
| 70 | |
| 71 | /* |
| 72 | * Debug code: |
| 73 | * |
| 74 | * This is not conditionally compiled since it may be useful to third |
| 75 | * parties when developing new modules. |
| 76 | */ |
| 77 | |
| 78 | #define BUFSZ 512 |
| 79 | |
| 80 | #define FMODSW_INIT 0x00000001 |
| 81 | #define FMODSW_REGISTER 0x00000002 |
| 82 | #define FMODSW_UNREGISTER 0x00000004 |
| 83 | #define FMODSW_FIND 0x00000008 |
| 84 | |
| 85 | uint32_t fmodsw_debug_flags = 0x00000000; |
| 86 | |
| 87 | static void fmodsw_dprintf(uint_t flag, const char *fmt, ...) __KPRINTFLIKE(2); |
| 88 | |
| 89 | /* PRINTFLIKE2 */ |
| 90 | static void |
| 91 | i_fmodsw_dprintf(uint_t flag, const char *fmt, ...) |
| 92 | { |
| 93 | va_list alist; |
| 94 | char buf[BUFSZ + 1]; |
| 95 | char *ptr; |
| 96 | |
| 97 | if (fmodsw_debug_flags & flag) { |
| 98 | va_start(alist, fmt); |
| 99 | ptr = buf; |
| 100 | (void) sprintf(ptr, "strmod debug: "); |
| 101 | ptr += strlen(buf); |
| 102 | (void) vsnprintf(ptr, buf + BUFSZ - ptr, fmt, alist); |
| 103 | printf(buf); |
| 104 | va_end(alist); |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | |
| 109 | /* |
| 110 | * Local functions: |
| 111 | */ |
| 112 | |
| 113 | #define FMODSW_HASH(_key) \ |
| 114 | (uint_t)(((_key[0] << 4) | (_key[1] & 0x0f)) & (FMODSW_HASHSZ - 1)) |
| 115 | |
| 116 | #define FMODSW_KEYCMP(_k1, _k2, _match) \ |
| 117 | { \ |
| 118 | char *p1 = (char *)(_k1); \ |
| 119 | char *p2 = (char *)(_k2); \ |
| 120 | \ |
| 121 | while (*p1 == *p2++) { \ |
| 122 | if (*p1++ == '\0') { \ |
| 123 | goto _match; \ |
| 124 | } \ |
| 125 | } \ |
| 126 | } |
| 127 | |
| 128 | static int |
| 129 | i_fmodsw_hash_insert(fmodsw_impl_t *fp) |
| 130 | { |
| 131 | uint_t bucket; |
| 132 | fmodsw_impl_t **pp; |
| 133 | fmodsw_impl_t *p; |
| 134 | |
| 135 | ASSERT(rw_write_held(&fmodsw_lock)); |
| 136 | |
| 137 | bucket = FMODSW_HASH(fp->f_name); |
| 138 | for (pp = &(fmodsw_hash[bucket]); (p = *pp) != NULL; |
| 139 | pp = &(p->f_next)) |
| 140 | FMODSW_KEYCMP(p->f_name, fp->f_name, found); |
| 141 | |
| 142 | fp->f_next = p; |
| 143 | *pp = fp; |
| 144 | return (0); |
| 145 | |
| 146 | found: |
| 147 | return (EEXIST); |
| 148 | } |
| 149 | |
| 150 | static int |
| 151 | i_fmodsw_hash_remove(const char *name, fmodsw_impl_t **fpp) |
| 152 | { |
| 153 | uint_t bucket; |
| 154 | fmodsw_impl_t **pp; |
| 155 | fmodsw_impl_t *p; |
| 156 | |
| 157 | ASSERT(rw_write_held(&fmodsw_lock)); |
| 158 | |
| 159 | bucket = FMODSW_HASH(name); |
| 160 | for (pp = &(fmodsw_hash[bucket]); (p = *pp) != NULL; |
| 161 | pp = &(p->f_next)) |
| 162 | FMODSW_KEYCMP(p->f_name, name, found); |
| 163 | |
| 164 | return (ENOENT); |
| 165 | |
| 166 | found: |
| 167 | if (p->f_ref > 0) |
| 168 | return (EBUSY); |
| 169 | |
| 170 | *pp = p->f_next; |
| 171 | *fpp = p; |
| 172 | return (0); |
| 173 | } |
| 174 | |
| 175 | static int |
| 176 | i_fmodsw_hash_find(const char *name, fmodsw_impl_t **fpp) |
| 177 | { |
| 178 | uint_t bucket; |
| 179 | fmodsw_impl_t *p; |
| 180 | int rc = 0; |
| 181 | |
| 182 | ASSERT(rw_read_held(&fmodsw_lock)); |
| 183 | |
| 184 | bucket = FMODSW_HASH(name); |
| 185 | for (p = fmodsw_hash[bucket]; p != NULL; p = p->f_next) |
| 186 | FMODSW_KEYCMP(p->f_name, name, found); |
| 187 | |
| 188 | rc = ENOENT; |
| 189 | found: |
| 190 | *fpp = p; |
| 191 | #ifdef DEBUG |
| 192 | if (p != NULL) |
| 193 | p->f_hits++; |
| 194 | #endif /* DEBUG */ |
| 195 | |
| 196 | return (rc); |
| 197 | } |
| 198 | |
| 199 | |
| 200 | /* |
| 201 | * Exported functions: |
| 202 | */ |
| 203 | |
| 204 | int |
| 205 | fmodsw_register(const char *name, struct streamtab *str, int flag) |
| 206 | { |
| 207 | fmodsw_impl_t *fp; |
| 208 | int len; |
| 209 | int err; |
| 210 | uint_t qflag; |
| 211 | uint_t sqtype; |
| 212 | |
| 213 | if ((len = strlen(name)) > FMNAMESZ) |
| 214 | return (EINVAL); |
| 215 | |
| 216 | if ((fp = kmem_zalloc(sizeof (fmodsw_impl_t), KM_NOSLEEP)) == NULL) |
| 217 | return (ENOMEM); |
| 218 | |
| 219 | (void) strncpy(fp->f_name, name, len); |
| 220 | fp->f_name[len] = '\0'; |
| 221 | |
| 222 | if ((err = devflg_to_qflag(str, flag, &qflag, &sqtype)) != 0) |
| 223 | goto failed; |
| 224 | |
| 225 | fp->f_str = str; |
| 226 | fp->f_qflag = qflag; |
| 227 | fp->f_sqtype = sqtype; |
| 228 | if (qflag & (QPERMOD | QMTOUTPERIM)) |
| 229 | fp->f_dmp = hold_dm(str, qflag, sqtype); |
| 230 | |
| 231 | rw_enter(&fmodsw_lock, RW_WRITER); |
| 232 | if ((err = i_fmodsw_hash_insert(fp)) != 0) { |
| 233 | rw_exit(&fmodsw_lock); |
| 234 | goto failed; |
| 235 | } |
| 236 | rw_exit(&fmodsw_lock); |
| 237 | |
| 238 | i_fmodsw_dprintf(FMODSW_REGISTER, "registered module '%s'\n", name); |
| 239 | return (0); |
| 240 | failed: |
| 241 | i_fmodsw_dprintf(FMODSW_REGISTER, "failed to register module '%s'\n", |
| 242 | name); |
| 243 | if (fp->f_dmp != NULL) |
| 244 | rele_dm(fp->f_dmp); |
| 245 | kmem_free(fp, sizeof (fmodsw_impl_t)); |
| 246 | return (err); |
| 247 | } |
| 248 | |
| 249 | int |
| 250 | fmodsw_unregister(const char *name) |
| 251 | { |
| 252 | fmodsw_impl_t *fp; |
| 253 | int err; |
| 254 | |
| 255 | rw_enter(&fmodsw_lock, RW_WRITER); |
| 256 | if ((err = i_fmodsw_hash_remove(name, &fp)) != 0) { |
| 257 | rw_exit(&fmodsw_lock); |
| 258 | goto failed; |
| 259 | } |
| 260 | rw_exit(&fmodsw_lock); |
| 261 | |
| 262 | if (fp->f_dmp != NULL) |
| 263 | rele_dm(fp->f_dmp); |
| 264 | kmem_free(fp, sizeof (fmodsw_impl_t)); |
| 265 | |
| 266 | i_fmodsw_dprintf(FMODSW_UNREGISTER, "unregistered module '%s'\n", |
| 267 | name); |
| 268 | return (0); |
| 269 | failed: |
| 270 | i_fmodsw_dprintf(FMODSW_UNREGISTER, "failed to unregister module " |
| 271 | "'%s'\n", name); |
| 272 | return (err); |
| 273 | } |
| 274 | |
| 275 | fmodsw_impl_t * |
| 276 | fmodsw_find(const char *name, fmodsw_flags_t flags) |
| 277 | { |
| 278 | fmodsw_impl_t *fp; |
| 279 | int id; |
| 280 | |
| 281 | try_again: |
| 282 | rw_enter(&fmodsw_lock, RW_READER); |
| 283 | if (i_fmodsw_hash_find(name, &fp) == 0) { |
| 284 | if (flags & FMODSW_HOLD) { |
Josef 'Jeff' Sipek | 1a5e258 | 2014-08-08 10:50:14 -0400 | [diff] [blame] | 285 | atomic_inc_32(&(fp->f_ref)); /* lock must be held */ |
stevel@tonic-gate | 7c478bd | 2005-06-14 00:00:00 -0700 | [diff] [blame] | 286 | ASSERT(fp->f_ref > 0); |
| 287 | } |
| 288 | |
| 289 | rw_exit(&fmodsw_lock); |
| 290 | return (fp); |
| 291 | } |
| 292 | rw_exit(&fmodsw_lock); |
| 293 | |
| 294 | if (flags & FMODSW_LOAD) { |
| 295 | if ((id = modload("strmod", (char *)name)) != -1) { |
| 296 | i_fmodsw_dprintf(FMODSW_FIND, |
| 297 | "module '%s' loaded: id = %d\n", name, id); |
| 298 | goto try_again; |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | return (NULL); |
| 303 | } |
| 304 | |
| 305 | void |
| 306 | fmodsw_rele(fmodsw_impl_t *fp) |
| 307 | { |
| 308 | ASSERT(fp->f_ref > 0); |
Josef 'Jeff' Sipek | 1a5e258 | 2014-08-08 10:50:14 -0400 | [diff] [blame] | 309 | atomic_dec_32(&(fp->f_ref)); |
stevel@tonic-gate | 7c478bd | 2005-06-14 00:00:00 -0700 | [diff] [blame] | 310 | } |