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