1/*	$NetBSD: simplehook_tester.c,v 1.2 2022/04/10 09:50:46 andvar Exp $	*/
2/*
3 * Copyright (c) 2021 Internet Initiative Japan Inc.
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
16 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
17 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
19 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include <sys/cdefs.h>
29__KERNEL_RCSID(0, "$NetBSD: simplehook_tester.c,v 1.2 2022/04/10 09:50:46 andvar Exp $");
30
31#include <sys/param.h>
32
33#include <sys/condvar.h>
34#include <sys/hook.h>
35#include <sys/module.h>
36#include <sys/mutex.h>
37#include <sys/sysctl.h>
38#include <sys/workqueue.h>
39
40#ifdef SIMPLEHOOK_TESTER_DEBUG
41#define HK_DPRINTF(a)	printf a
42#else
43#define HK_DPRINTF(a)	__nothing
44#endif
45
46MODULE(MODULE_CLASS_MISC, simplehook_tester, NULL);
47extern int simplehook_tester_init(void);
48struct tester_context;
49
50struct tester_hook {
51	struct tester_context	*th_ctx;
52	khook_t			*th_hook;
53	size_t			 th_idx;
54	int			 th_count;
55	bool			 th_stopping;
56	bool			 th_stopped;
57	bool			 th_disestablish;
58};
59
60static struct tester_context {
61	kmutex_t		 ctx_mutex;
62	kcondvar_t		 ctx_cv;
63	struct sysctllog	*ctx_sysctllog;
64	struct workqueue	*ctx_wq;
65	struct work		 ctx_wk;
66	bool			 ctx_wk_enqueued;
67	bool			 ctx_wk_waiting;
68
69	khook_list_t		*ctx_hooks;
70	struct tester_hook	 ctx_hook[2];
71
72	khook_t			*ctx_nbhook;
73} tester_ctx;
74
75static int
76simplehook_tester_created(SYSCTLFN_ARGS)
77{
78	struct sysctlnode node;
79	struct tester_context *ctx;
80	int error, val;
81	size_t i;
82
83	node = *rnode;
84	ctx = node.sysctl_data;
85
86	mutex_enter(&ctx->ctx_mutex);
87	val = ctx->ctx_hooks != NULL ? 1 : 0;
88	mutex_exit(&ctx->ctx_mutex);
89
90	node.sysctl_data = &val;
91	node.sysctl_size = sizeof(val);
92
93	error = sysctl_lookup(SYSCTLFN_CALL(&node));
94	if (error || newp == NULL)
95		return error;
96
97	if (val != 0 && val != 1)
98		return EINVAL;
99
100	error = 0;
101	mutex_enter(&ctx->ctx_mutex);
102	if (val == 1) {
103		if (ctx->ctx_hooks != NULL) {
104			error = EEXIST;
105		} else {
106			HK_DPRINTF(("[%s, %d]: create hook list\n",
107			    __func__, __LINE__));
108			ctx->ctx_hooks = simplehook_create(IPL_NONE,
109			    "tester hooks");
110			KASSERT(ctx->ctx_hooks != NULL);
111		}
112	} else {
113		if (ctx->ctx_hooks == NULL) {
114			error = ENXIO;
115		} else if (ctx->ctx_wk_waiting) {
116			error = EBUSY;
117		} else {
118			ctx->ctx_wk_waiting = true;
119			mutex_exit(&ctx->ctx_mutex);
120
121			workqueue_wait(ctx->ctx_wq, &ctx->ctx_wk);
122
123			mutex_enter(&ctx->ctx_mutex);
124			ctx->ctx_wk_waiting = false;
125
126			HK_DPRINTF(("[%s, %d]: destroy hook list\n",
127			    __func__, __LINE__));
128			simplehook_destroy(ctx->ctx_hooks);
129			ctx->ctx_hooks = NULL;
130			ctx->ctx_nbhook = NULL;
131			for (i = 0; i < __arraycount(ctx->ctx_hook); i++) {
132				ctx->ctx_hook[i].th_hook = NULL;
133			}
134		}
135	}
136	mutex_exit(&ctx->ctx_mutex);
137
138	return error;
139}
140
141static void
142simplehook_tester_work(struct work *wk, void *xctx)
143{
144	struct tester_context *ctx;
145
146	ctx = xctx;
147
148	mutex_enter(&ctx->ctx_mutex);
149	ctx->ctx_wk_enqueued = false;
150	mutex_exit(&ctx->ctx_mutex);
151
152	simplehook_dohooks(ctx->ctx_hooks);
153}
154
155static int
156simplehook_tester_dohooks(SYSCTLFN_ARGS)
157{
158	struct sysctlnode node;
159	struct tester_context *ctx;
160	int error, val;
161
162	node = *rnode;
163	ctx = node.sysctl_data;
164
165	mutex_enter(&ctx->ctx_mutex);
166	val = ctx->ctx_wk_enqueued ? 1 : 0;
167	mutex_exit(&ctx->ctx_mutex);
168
169	node.sysctl_data = &val;
170	node.sysctl_size = sizeof(val);
171
172	error = sysctl_lookup(SYSCTLFN_CALL(&node));
173	if (error || newp == NULL)
174		return error;
175
176	if (val != 0 && val != 1)
177		return EINVAL;
178
179	mutex_enter(&ctx->ctx_mutex);
180	if (val == 1) {
181		if (ctx->ctx_wk_enqueued) {
182			error = EEXIST;
183		} else if (ctx->ctx_wk_waiting) {
184			error = EBUSY;
185		} else if (ctx->ctx_hooks == NULL) {
186			error = ENXIO;
187		} else {
188			HK_DPRINTF(("[%s, %d]: dohook\n", __func__, __LINE__));
189			ctx->ctx_wk_enqueued = true;
190			workqueue_enqueue(ctx->ctx_wq,
191			    &ctx->ctx_wk, NULL);
192		}
193	} else {
194		if (ctx->ctx_wk_waiting) {
195			error = EBUSY;
196		} else {
197			ctx->ctx_wk_waiting = true;
198			mutex_exit(&ctx->ctx_mutex);
199
200			workqueue_wait(ctx->ctx_wq, &ctx->ctx_wk);
201
202			mutex_enter(&ctx->ctx_mutex);
203			ctx->ctx_wk_waiting = false;
204		}
205	}
206	mutex_exit(&ctx->ctx_mutex);
207
208	return error;
209}
210
211static void
212simplehook_tester_hook(void *xth)
213{
214	struct tester_context *ctx;
215	struct tester_hook *th;
216
217	th = xth;
218	ctx = th->th_ctx;
219	mutex_enter(&ctx->ctx_mutex);
220
221	HK_DPRINTF(("[%s, %d]: hook%zu called\n",
222	    __func__, __LINE__, th->th_idx));
223
224	th->th_stopped = false;
225
226	while (th->th_stopping) {
227		HK_DPRINTF(("[%s, %d]: hook%zu stopping\n",
228		    __func__, __LINE__, th->th_idx));
229		th->th_stopped = true;
230		cv_wait(&ctx->ctx_cv, &ctx->ctx_mutex);
231	}
232
233	if (th->th_stopped) {
234		HK_DPRINTF(("[%s, %d]: hook%zu restart\n",
235		    __func__, __LINE__, th->th_idx));
236		th->th_stopped = false;
237	}
238
239	th->th_count++;
240
241	if (th->th_disestablish && th->th_hook != NULL) {
242		HK_DPRINTF(("[%s, %d]: disestablish running hook%zu\n",
243		    __func__, __LINE__, th->th_idx));
244		simplehook_disestablish(ctx->ctx_hooks,
245		    th->th_hook, &ctx->ctx_mutex);
246		th->th_hook = NULL;
247	}
248
249	HK_DPRINTF(("[%s, %d]: hook%zu exit\n",
250	    __func__, __LINE__, th->th_idx));
251
252	mutex_exit(&ctx->ctx_mutex);
253}
254
255static void
256simplehook_tester_hook_nb(void *xctx)
257{
258
259	HK_DPRINTF(("[%s, %d]: non-block hook called\n",
260	    __func__, __LINE__));
261
262	HK_DPRINTF(("[%s, %d]: sleep 1 sec\n",
263	    __func__, __LINE__));
264	kpause("smplhk_nb", true, 1 * hz, NULL);
265
266	HK_DPRINTF(("[%s, %d]: non-block hook exit\n",
267	    __func__, __LINE__));
268}
269
270static int
271simplehook_tester_established(SYSCTLFN_ARGS)
272{
273	struct sysctlnode node;
274	struct tester_context *ctx;
275	struct tester_hook *th;
276	int val, error;
277
278	node = *rnode;
279	th = node.sysctl_data;
280	ctx = th->th_ctx;
281
282	mutex_enter(&ctx->ctx_mutex);
283	val = th->th_hook == NULL ? 0 : 1;
284	mutex_exit(&ctx->ctx_mutex);
285
286	node.sysctl_data = &val;
287	node.sysctl_size = sizeof(val);
288
289	error = sysctl_lookup(SYSCTLFN_CALL(&node));
290	if (error || newp == NULL)
291		return error;
292
293	error = 0;
294	mutex_enter(&ctx->ctx_mutex);
295
296	if (val == 1) {
297		if (th->th_hook != NULL) {
298			error = EEXIST;
299		} else {
300			th->th_hook = simplehook_establish(ctx->ctx_hooks,
301			    simplehook_tester_hook, th);
302			KASSERT(th->th_hook != NULL);
303			HK_DPRINTF(("[%s, %d]: established hook%zu (%p)\n",
304			    __func__, __LINE__, th->th_idx, th->th_hook));
305		}
306	} else {
307		if (th->th_hook == NULL) {
308			error = ENXIO;
309		} else {
310			bool stopped = false;
311			if (th->th_stopping) {
312				HK_DPRINTF(("[%s, %d]: stopping = false\n",
313				    __func__, __LINE__));
314				th->th_stopping = false;
315				cv_broadcast(&ctx->ctx_cv);
316				stopped = true;
317			}
318			HK_DPRINTF(("[%s, %d]: disestablish hook%zu (%p)\n",
319			    __func__, __LINE__, th->th_idx, th->th_hook));
320			simplehook_disestablish(ctx->ctx_hooks,
321			    th->th_hook, &ctx->ctx_mutex);
322			th->th_hook = NULL;
323			if (stopped) {
324				HK_DPRINTF(("[%s, %d]: disestablished hook%zu\n",
325				    __func__, __LINE__, th->th_idx));
326			}
327		}
328	}
329
330	mutex_exit(&ctx->ctx_mutex);
331
332	return error;
333}
334
335static int
336simplehook_tester_established_nb(SYSCTLFN_ARGS)
337{
338	struct sysctlnode node;
339	struct tester_context *ctx;
340	int val, error;
341
342	node = *rnode;
343	ctx = node.sysctl_data;
344
345	mutex_enter(&ctx->ctx_mutex);
346	val = ctx->ctx_nbhook == NULL ? 0 : 1;
347	mutex_exit(&ctx->ctx_mutex);
348
349	node.sysctl_data = &val;
350	node.sysctl_size = sizeof(val);
351
352	error = sysctl_lookup(SYSCTLFN_CALL(&node));
353	if (error || newp == NULL)
354		return error;
355
356	error = 0;
357	mutex_enter(&ctx->ctx_mutex);
358
359	if (val == 1) {
360		if (ctx->ctx_nbhook != NULL) {
361			error = EEXIST;
362		} else {
363			ctx->ctx_nbhook = simplehook_establish(ctx->ctx_hooks,
364			    simplehook_tester_hook_nb, ctx);
365			KASSERT(ctx->ctx_nbhook != NULL);
366			HK_DPRINTF(("[%s, %d]: established nbhook (%p)\n",
367			    __func__, __LINE__, ctx->ctx_nbhook));
368		}
369	} else {
370		if (ctx->ctx_nbhook == NULL) {
371			error = ENXIO;
372		} else {
373			HK_DPRINTF(("[%s, %d]: disestablish nbhook (%p)\n",
374			    __func__, __LINE__, ctx->ctx_nbhook));
375			simplehook_disestablish(ctx->ctx_hooks,
376			    ctx->ctx_nbhook, NULL);
377			ctx->ctx_nbhook = NULL;
378			HK_DPRINTF(("[%s, %d]: disestablished\n",
379			    __func__, __LINE__));
380		}
381	}
382
383	mutex_exit(&ctx->ctx_mutex);
384
385	return error;
386}
387
388static int
389simplehook_tester_stopping(SYSCTLFN_ARGS)
390{
391	struct sysctlnode node;
392	struct tester_context *ctx;
393	struct tester_hook *th;
394	int error;
395	bool val;
396
397	node = *rnode;
398	th = node.sysctl_data;
399	ctx = th->th_ctx;
400
401	mutex_enter(&ctx->ctx_mutex);
402	val = th->th_stopping;
403	mutex_exit(&ctx->ctx_mutex);
404
405	node.sysctl_data = &val;
406	node.sysctl_size = sizeof(val);
407
408	error = sysctl_lookup(SYSCTLFN_CALL(&node));
409	if (error || newp == NULL)
410		return error;
411
412	error = 0;
413	mutex_enter(&ctx->ctx_mutex);
414	if (val == true && !th->th_stopping) {
415		th->th_stopping = true;
416	} else if (val == false && th->th_stopping) {
417		th->th_stopping = false;
418		cv_broadcast(&ctx->ctx_cv);
419	}
420	mutex_exit(&ctx->ctx_mutex);
421
422	return error;
423}
424
425static int
426simplehook_tester_stopped(SYSCTLFN_ARGS)
427{
428	struct sysctlnode node;
429	struct tester_context *ctx;
430	struct tester_hook *th;
431	bool val;
432
433	node = *rnode;
434	th = node.sysctl_data;
435	ctx = th->th_ctx;
436
437	mutex_enter(&ctx->ctx_mutex);
438	val = th->th_stopped;
439	mutex_exit(&ctx->ctx_mutex);
440
441	node.sysctl_data = &val;
442	node.sysctl_size = sizeof(val);
443
444	return sysctl_lookup(SYSCTLFN_CALL(&node));
445}
446
447static int
448simplehook_tester_disestablish(SYSCTLFN_ARGS)
449{
450	struct sysctlnode node;
451	struct tester_context *ctx;
452	struct tester_hook *th;
453	int error;
454	bool val;
455
456	node = *rnode;
457	th = node.sysctl_data;
458	ctx = th->th_ctx;
459
460	mutex_enter(&ctx->ctx_mutex);
461	val = th->th_disestablish;
462	mutex_exit(&ctx->ctx_mutex);
463
464	node.sysctl_data = &val;
465	node.sysctl_size = sizeof(val);
466
467	error = sysctl_lookup(SYSCTLFN_CALL(&node));
468	if (error || newp == NULL)
469		return error;
470
471	if (val != 0 && val != 1)
472		return EINVAL;
473
474	error = 0;
475	mutex_enter(&ctx->ctx_mutex);
476	if (val == true && !th->th_disestablish) {
477		th->th_disestablish = true;
478	} else if (val == false && th->th_disestablish) {
479		th->th_disestablish = false;
480	}
481	mutex_exit(&ctx->ctx_mutex);
482
483	return 0;
484}
485
486static int
487simplehook_tester_count(SYSCTLFN_ARGS)
488{
489	struct sysctlnode node;
490	struct tester_context *ctx;
491	struct tester_hook *th;
492	int error, val;
493
494	node = *rnode;
495	th = node.sysctl_data;
496	ctx = th->th_ctx;
497
498	mutex_enter(&ctx->ctx_mutex);
499	val = th->th_count;
500	mutex_exit(&ctx->ctx_mutex);
501
502	node.sysctl_data = &val;
503	node.sysctl_size = sizeof(val);
504
505	error = sysctl_lookup(SYSCTLFN_CALL(&node));
506	if (error || newp == NULL)
507		return error;
508
509	mutex_enter(&ctx->ctx_mutex);
510	th->th_count = val;
511	mutex_exit(&ctx->ctx_mutex);
512
513	return 0;
514}
515
516static int
517simplehook_tester_create_sysctl(struct tester_context *ctx)
518{
519	struct sysctllog **log;
520	const struct sysctlnode *rnode, *cnode;
521	void *ptr;
522	char buf[32];
523	int error;
524	size_t i;
525
526	log = &ctx->ctx_sysctllog;
527	ptr = (void *)ctx;
528
529	error = sysctl_createv(log, 0, NULL, &rnode, CTLFLAG_PERMANENT,
530	    CTLTYPE_NODE, "simplehook_tester",
531	    SYSCTL_DESCR("simplehook testing interface"),
532	    NULL, 0, NULL, 0, CTL_KERN, CTL_CREATE, CTL_EOL);
533	if (error != 0)
534		goto bad;
535
536	error = sysctl_createv(log, 0, &rnode, &cnode,
537	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "hook_list",
538	    SYSCTL_DESCR("hook list"), NULL, 0, NULL, 0,
539	    CTL_CREATE, CTL_EOL);
540
541	error = sysctl_createv(log, 0, &cnode, NULL,
542	    CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT,
543	    "created", SYSCTL_DESCR("create and destroy hook list"),
544	    simplehook_tester_created, 0, ptr, 0,
545	    CTL_CREATE, CTL_EOL);
546	if (error != 0)
547		goto bad;
548
549	error = sysctl_createv(log, 0, &cnode, NULL,
550	    CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_INT,
551	    "dohooks", SYSCTL_DESCR("do hooks"),
552	    simplehook_tester_dohooks, 0, ptr, 0,
553	    CTL_CREATE, CTL_EOL);
554	if (error != 0)
555		goto bad;
556
557	error = sysctl_createv(log, 0, &rnode, &cnode,
558	    CTLFLAG_PERMANENT, CTLTYPE_NODE, "nbhook",
559	    SYSCTL_DESCR("non-blocking hook"), NULL, 0, NULL, 0,
560	    CTL_CREATE, CTL_EOL);
561	if (error != 0)
562		goto bad;
563
564	error = sysctl_createv(log, 0, &cnode, NULL,
565	    CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
566	    CTLTYPE_INT, "established",
567	    SYSCTL_DESCR("establish and disestablish hook"),
568	    simplehook_tester_established_nb,
569	    0, ptr, 0, CTL_CREATE, CTL_EOL);
570	if (error != 0)
571		goto bad;
572
573	for (i = 0; i < __arraycount(ctx->ctx_hook); i++) {
574		snprintf(buf, sizeof(buf), "hook%zu", i);
575		ptr = (void *)&ctx->ctx_hook[i];
576
577		error = sysctl_createv(log, 0, &rnode, &cnode,
578		    CTLFLAG_PERMANENT, CTLTYPE_NODE, buf,
579		    SYSCTL_DESCR("hook information"), NULL, 0, NULL, 0,
580		    CTL_CREATE, CTL_EOL);
581		if (error != 0)
582			goto bad;
583
584		error = sysctl_createv(log, 0, &cnode, NULL,
585		    CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
586		    CTLTYPE_INT, "established",
587		    SYSCTL_DESCR("establish and disestablish hook"),
588		    simplehook_tester_established,
589		    0, ptr, 0, CTL_CREATE, CTL_EOL);
590		if (error != 0)
591			goto bad;
592
593		error = sysctl_createv(log, 0, &cnode, NULL,
594		    CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
595		    CTLTYPE_BOOL, "stopping",
596		    SYSCTL_DESCR("stopping at beginning of the hook"),
597		    simplehook_tester_stopping, 0, ptr, 0,
598		    CTL_CREATE, CTL_EOL);
599		if (error != 0)
600			goto bad;
601
602		error = sysctl_createv(log, 0, &cnode, NULL,
603		    CTLFLAG_PERMANENT|CTLFLAG_READONLY,
604		    CTLTYPE_BOOL, "stopped",
605		    SYSCTL_DESCR("the hook is stopped"),
606		    simplehook_tester_stopped, 0, ptr, 0,
607		    CTL_CREATE, CTL_EOL);
608		if (error != 0)
609			goto bad;
610
611		error = sysctl_createv(log, 0, &cnode, NULL,
612		    CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_BOOL,
613		    "disestablish_in_hook",
614		    SYSCTL_DESCR("disestablish this hook in it"),
615		    simplehook_tester_disestablish, 0, ptr, 0,
616		    CTL_CREATE, CTL_EOL);
617		if (error != 0)
618			goto bad;
619
620		error = sysctl_createv(log, 0, &cnode, NULL,
621		    CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
622		    CTLTYPE_INT, "count",
623		    SYSCTL_DESCR("the number of calls of the hook"),
624		    simplehook_tester_count, 0, ptr, 0,
625		    CTL_CREATE, CTL_EOL);
626		if (error != 0)
627			goto bad;
628	}
629
630	HK_DPRINTF(("[%s, %d]: created sysctls\n", __func__, __LINE__));
631	return 0;
632
633bad:
634	sysctl_teardown(log);
635	return error;
636}
637
638static void
639simplehook_tester_init_ctx(struct tester_context *ctx)
640{
641	size_t i;
642
643	memset(ctx, 0, sizeof(*ctx));
644	mutex_init(&ctx->ctx_mutex, MUTEX_DEFAULT, IPL_NONE);
645	workqueue_create(&ctx->ctx_wq, "shook_tester_wq",
646	    simplehook_tester_work, ctx, PRI_NONE, IPL_NONE, WQ_MPSAFE);
647	cv_init(&ctx->ctx_cv, "simplehook_tester_cv");
648
649	for (i = 0; i < __arraycount(ctx->ctx_hook); i++) {
650		ctx->ctx_hook[i].th_ctx = ctx;
651		ctx->ctx_hook[i].th_idx = i;
652	}
653}
654
655
656int
657simplehook_tester_init(void)
658{
659	int error;
660
661	simplehook_tester_init_ctx(&tester_ctx);
662	error = simplehook_tester_create_sysctl(&tester_ctx);
663
664	return error;
665}
666
667static int
668simplehook_tester_fini(void)
669{
670	struct tester_context *ctx;
671	struct tester_hook *th;
672	khook_list_t *hooks;
673	size_t i;
674
675	ctx = &tester_ctx;
676
677	sysctl_teardown(&ctx->ctx_sysctllog);
678
679	mutex_enter(&ctx->ctx_mutex);
680
681	hooks = ctx->ctx_hooks;
682	ctx->ctx_hooks = NULL;
683	ctx->ctx_wk_waiting = true;
684
685	for (i = 0; i < __arraycount(ctx->ctx_hook); i++) {
686		th = &ctx->ctx_hook[i];
687		th->th_stopping = false;
688	}
689	cv_broadcast(&ctx->ctx_cv);
690
691	mutex_exit(&ctx->ctx_mutex);
692
693	workqueue_wait(ctx->ctx_wq, &ctx->ctx_wk);
694
695	workqueue_destroy(ctx->ctx_wq);
696	if (hooks != NULL)
697		simplehook_destroy(hooks);
698	cv_destroy(&ctx->ctx_cv);
699	mutex_destroy(&ctx->ctx_mutex);
700
701	return 0;
702}
703
704static int
705simplehook_tester_modcmd(modcmd_t cmd, void *arg __unused)
706{
707	int error;
708
709	switch (cmd) {
710	case MODULE_CMD_INIT:
711		error = simplehook_tester_init();
712		break;
713
714	case MODULE_CMD_FINI:
715		error = simplehook_tester_fini();
716		break;
717
718	case MODULE_CMD_STAT:
719	default:
720		error = ENOTTY;
721	}
722
723	return error;
724}
725