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