1 /* 2 * (MPSAFE) 3 * 4 * Copyright (c) 1995 Ugen J.S.Antsilevich 5 * 6 * Redistribution and use in source forms, with and without modification, 7 * are permitted provided that this entire comment appears intact. 8 * 9 * Redistribution in binary form may occur without any restrictions. 10 * Obviously, it would be nice if you gave credit where credit is due 11 * but requiring it would be too onerous. 12 * 13 * This software is provided ``AS IS'' without any warranties of any kind. 14 * 15 * Snoop stuff. 16 * 17 * $FreeBSD: src/sys/dev/snp/snp.c,v 1.69.2.2 2002/05/06 07:30:02 dd Exp $ 18 */ 19 20 #include "use_snp.h" 21 #include <sys/param.h> 22 #include <sys/systm.h> 23 #include <sys/filio.h> 24 #include <sys/malloc.h> 25 #include <sys/tty.h> 26 #include <sys/conf.h> 27 #include <sys/event.h> 28 #include <sys/kernel.h> 29 #include <sys/queue.h> 30 #include <sys/snoop.h> 31 #include <sys/thread2.h> 32 #include <sys/vnode.h> 33 #include <sys/device.h> 34 #include <sys/devfs.h> 35 36 static l_close_t snplclose; 37 static l_write_t snplwrite; 38 static d_open_t snpopen; 39 static d_close_t snpclose; 40 static d_read_t snpread; 41 static d_write_t snpwrite; 42 static d_ioctl_t snpioctl; 43 static d_kqfilter_t snpkqfilter; 44 static d_clone_t snpclone; 45 DEVFS_DECLARE_CLONE_BITMAP(snp); 46 47 static void snpfilter_detach(struct knote *); 48 static int snpfilter_rd(struct knote *, long); 49 static int snpfilter_wr(struct knote *, long); 50 51 #if NSNP <= 1 52 #define SNP_PREALLOCATED_UNITS 4 53 #else 54 #define SNP_PREALLOCATED_UNITS NSNP 55 #endif 56 57 static struct dev_ops snp_ops = { 58 { "snp", 0, 0 }, 59 .d_open = snpopen, 60 .d_close = snpclose, 61 .d_read = snpread, 62 .d_write = snpwrite, 63 .d_ioctl = snpioctl, 64 .d_kqfilter = snpkqfilter 65 }; 66 67 static struct linesw snpdisc = { 68 ttyopen, snplclose, ttread, snplwrite, 69 l_nullioctl, ttyinput, ttstart, ttymodem 70 }; 71 72 /* 73 * This is the main snoop per-device structure. 74 */ 75 struct snoop { 76 LIST_ENTRY(snoop) snp_list; /* List glue. */ 77 cdev_t snp_target; /* Target tty device. */ 78 struct tty *snp_tty; /* Target tty pointer. */ 79 u_long snp_len; /* Possible length. */ 80 u_long snp_base; /* Data base. */ 81 u_long snp_blen; /* Used length. */ 82 caddr_t snp_buf; /* Allocation pointer. */ 83 int snp_flags; /* Flags. */ 84 struct kqinfo snp_kq; /* Kqueue info. */ 85 int snp_olddisc; /* Old line discipline. */ 86 }; 87 88 /* 89 * Possible flags. 90 */ 91 #define SNOOP_ASYNC 0x0002 92 #define SNOOP_OPEN 0x0004 93 #define SNOOP_RWAIT 0x0008 94 #define SNOOP_OFLOW 0x0010 95 #define SNOOP_DOWN 0x0020 96 97 /* 98 * Other constants. 99 */ 100 #define SNOOP_MINLEN (4*1024) /* This should be power of 2. 101 * 4K tested to be the minimum 102 * for which on normal tty 103 * usage there is no need to 104 * allocate more. 105 */ 106 #define SNOOP_MAXLEN (64*1024) /* This one also,64K enough 107 * If we grow more,something 108 * really bad in this world.. 109 */ 110 111 static MALLOC_DEFINE(M_SNP, "snp", "Snoop device data"); 112 /* 113 * The number of the "snoop" line discipline. This gets determined at 114 * module load time. 115 */ 116 static int snooplinedisc; 117 118 119 static LIST_HEAD(, snoop) snp_sclist = LIST_HEAD_INITIALIZER(&snp_sclist); 120 121 static struct tty *snpdevtotty (cdev_t dev); 122 static int snp_detach (struct snoop *snp); 123 static int snp_down (struct snoop *snp); 124 static int snp_in (struct snoop *snp, char *buf, int n); 125 static int snp_modevent (module_t mod, int what, void *arg); 126 127 static int 128 snplclose(struct tty *tp, int flag) 129 { 130 struct snoop *snp; 131 int error; 132 133 lwkt_gettoken(&tty_token); 134 snp = tp->t_sc; 135 error = snp_down(snp); 136 if (error != 0) { 137 lwkt_reltoken(&tty_token); 138 return (error); 139 } 140 error = ttylclose(tp, flag); 141 lwkt_reltoken(&tty_token); 142 return (error); 143 } 144 145 static int 146 snplwrite(struct tty *tp, struct uio *uio, int flag) 147 { 148 struct iovec iov; 149 struct uio uio2; 150 struct snoop *snp; 151 int error, ilen; 152 char *ibuf; 153 154 lwkt_gettoken(&tty_token); 155 error = 0; 156 ibuf = NULL; 157 snp = tp->t_sc; 158 while (uio->uio_resid > 0) { 159 ilen = (int)szmin(512, uio->uio_resid); 160 ibuf = kmalloc(ilen, M_SNP, M_WAITOK); 161 error = uiomove(ibuf, (size_t)ilen, uio); 162 if (error != 0) 163 break; 164 snp_in(snp, ibuf, ilen); 165 /* Hackish, but probably the least of all evils. */ 166 iov.iov_base = ibuf; 167 iov.iov_len = ilen; 168 uio2.uio_iov = &iov; 169 uio2.uio_iovcnt = 1; 170 uio2.uio_offset = 0; 171 uio2.uio_resid = ilen; 172 uio2.uio_segflg = UIO_SYSSPACE; 173 uio2.uio_rw = UIO_WRITE; 174 uio2.uio_td = uio->uio_td; 175 error = ttwrite(tp, &uio2, flag); 176 if (error != 0) 177 break; 178 kfree(ibuf, M_SNP); 179 ibuf = NULL; 180 } 181 if (ibuf != NULL) 182 kfree(ibuf, M_SNP); 183 lwkt_reltoken(&tty_token); 184 return (error); 185 } 186 187 static struct tty * 188 snpdevtotty(cdev_t dev) 189 { 190 if ((dev_dflags(dev) & D_TTY) == 0) 191 return (NULL); 192 return (dev->si_tty); 193 } 194 195 #define SNP_INPUT_BUF 5 /* This is even too much, the maximal 196 * interactive mode write is 3 bytes 197 * length for function keys... 198 */ 199 200 static int 201 snpwrite(struct dev_write_args *ap) 202 { 203 cdev_t dev = ap->a_head.a_dev; 204 struct uio *uio = ap->a_uio; 205 struct snoop *snp; 206 struct tty *tp; 207 int error, i, len; 208 unsigned char c[SNP_INPUT_BUF]; 209 210 lwkt_gettoken(&tty_token); 211 snp = dev->si_drv1; 212 tp = snp->snp_tty; 213 if (tp == NULL) { 214 lwkt_reltoken(&tty_token); 215 return (EIO); 216 } 217 if ((tp->t_sc == snp) && (tp->t_state & TS_SNOOP) && 218 tp->t_line == snooplinedisc) 219 goto tty_input; 220 221 kprintf("Snoop: attempt to write to bad tty.\n"); 222 lwkt_reltoken(&tty_token); 223 return (EIO); 224 225 tty_input: 226 if (!(tp->t_state & TS_ISOPEN)) { 227 lwkt_reltoken(&tty_token); 228 return (EIO); 229 } 230 231 while (uio->uio_resid > 0) { 232 len = (int)szmin(uio->uio_resid, SNP_INPUT_BUF); 233 if ((error = uiomove(c, (size_t)len, uio)) != 0) { 234 lwkt_reltoken(&tty_token); 235 return (error); 236 } 237 for (i=0; i < len; i++) { 238 if (ttyinput(c[i], tp)) { 239 lwkt_reltoken(&tty_token); 240 return (EIO); 241 } 242 } 243 } 244 lwkt_reltoken(&tty_token); 245 return (0); 246 } 247 248 249 static int 250 snpread(struct dev_read_args *ap) 251 { 252 cdev_t dev = ap->a_head.a_dev; 253 struct uio *uio = ap->a_uio; 254 struct snoop *snp; 255 int error, len, n, nblen; 256 caddr_t from; 257 char *nbuf; 258 259 lwkt_gettoken(&tty_token); 260 snp = dev->si_drv1; 261 KASSERT(snp->snp_len + snp->snp_base <= snp->snp_blen, 262 ("snoop buffer error")); 263 264 if (snp->snp_tty == NULL) { 265 lwkt_reltoken(&tty_token); 266 return (EIO); 267 } 268 269 snp->snp_flags &= ~SNOOP_RWAIT; 270 271 do { 272 if (snp->snp_len == 0) { 273 if (ap->a_ioflag & IO_NDELAY) { 274 lwkt_reltoken(&tty_token); 275 return (EWOULDBLOCK); 276 } 277 snp->snp_flags |= SNOOP_RWAIT; 278 error = tsleep((caddr_t)snp, PCATCH, "snprd", 0); 279 if (error != 0) { 280 lwkt_reltoken(&tty_token); 281 return (error); 282 } 283 } 284 } while (snp->snp_len == 0); 285 286 n = snp->snp_len; 287 288 error = 0; 289 while (snp->snp_len > 0 && uio->uio_resid > 0 && error == 0) { 290 len = (int)szmin(uio->uio_resid, snp->snp_len); 291 from = (caddr_t)(snp->snp_buf + snp->snp_base); 292 if (len == 0) 293 break; 294 295 error = uiomove(from, (size_t)len, uio); 296 snp->snp_base += len; 297 snp->snp_len -= len; 298 } 299 if ((snp->snp_flags & SNOOP_OFLOW) && (n < snp->snp_len)) { 300 snp->snp_flags &= ~SNOOP_OFLOW; 301 } 302 crit_enter(); 303 nblen = snp->snp_blen; 304 if (((nblen / 2) >= SNOOP_MINLEN) && (nblen / 2) >= snp->snp_len) { 305 while (nblen / 2 >= snp->snp_len && nblen / 2 >= SNOOP_MINLEN) 306 nblen = nblen / 2; 307 if ((nbuf = kmalloc(nblen, M_SNP, M_NOWAIT)) != NULL) { 308 bcopy(snp->snp_buf + snp->snp_base, nbuf, snp->snp_len); 309 kfree(snp->snp_buf, M_SNP); 310 snp->snp_buf = nbuf; 311 snp->snp_blen = nblen; 312 snp->snp_base = 0; 313 } 314 } 315 crit_exit(); 316 317 lwkt_reltoken(&tty_token); 318 return (error); 319 } 320 321 /* 322 * NOTE: Must be called with tty_token held 323 */ 324 static int 325 snp_in(struct snoop *snp, char *buf, int n) 326 { 327 int s_free, s_tail; 328 int len, nblen; 329 caddr_t from, to; 330 char *nbuf; 331 332 ASSERT_LWKT_TOKEN_HELD(&tty_token); 333 KASSERT(n >= 0, ("negative snoop char count")); 334 335 if (n == 0) 336 return (0); 337 338 if (snp->snp_flags & SNOOP_DOWN) { 339 kprintf("Snoop: more data to down interface.\n"); 340 return (0); 341 } 342 343 if (snp->snp_flags & SNOOP_OFLOW) { 344 kprintf("Snoop: buffer overflow.\n"); 345 /* 346 * On overflow we just repeat the standart close 347 * procedure...yes , this is waste of space but.. Then next 348 * read from device will fail if one would recall he is 349 * snooping and retry... 350 */ 351 352 return (snp_down(snp)); 353 } 354 s_tail = snp->snp_blen - (snp->snp_len + snp->snp_base); 355 s_free = snp->snp_blen - snp->snp_len; 356 357 358 if (n > s_free) { 359 crit_enter(); 360 nblen = snp->snp_blen; 361 while ((n > s_free) && ((nblen * 2) <= SNOOP_MAXLEN)) { 362 nblen = snp->snp_blen * 2; 363 s_free = nblen - (snp->snp_len + snp->snp_base); 364 } 365 if ((n <= s_free) && (nbuf = kmalloc(nblen, M_SNP, M_NOWAIT))) { 366 bcopy(snp->snp_buf + snp->snp_base, nbuf, snp->snp_len); 367 kfree(snp->snp_buf, M_SNP); 368 snp->snp_buf = nbuf; 369 snp->snp_blen = nblen; 370 snp->snp_base = 0; 371 } else { 372 snp->snp_flags |= SNOOP_OFLOW; 373 if (snp->snp_flags & SNOOP_RWAIT) { 374 snp->snp_flags &= ~SNOOP_RWAIT; 375 wakeup((caddr_t)snp); 376 } 377 crit_exit(); 378 return (0); 379 } 380 crit_exit(); 381 } 382 if (n > s_tail) { 383 from = (caddr_t)(snp->snp_buf + snp->snp_base); 384 to = (caddr_t)(snp->snp_buf); 385 len = snp->snp_len; 386 bcopy(from, to, len); 387 snp->snp_base = 0; 388 } 389 to = (caddr_t)(snp->snp_buf + snp->snp_base + snp->snp_len); 390 bcopy(buf, to, n); 391 snp->snp_len += n; 392 393 if (snp->snp_flags & SNOOP_RWAIT) { 394 snp->snp_flags &= ~SNOOP_RWAIT; 395 wakeup((caddr_t)snp); 396 } 397 KNOTE(&snp->snp_kq.ki_note, 0); 398 399 return (n); 400 } 401 402 static int 403 snpopen(struct dev_open_args *ap) 404 { 405 cdev_t dev = ap->a_head.a_dev; 406 struct snoop *snp; 407 408 lwkt_gettoken(&tty_token); 409 if (dev->si_drv1 == NULL) { 410 #if 0 411 make_dev(&snp_ops, minor(dev), UID_ROOT, GID_WHEEL, 412 0600, "snp%d", minor(dev)); 413 #endif 414 dev->si_drv1 = snp = kmalloc(sizeof(*snp), M_SNP, 415 M_WAITOK | M_ZERO); 416 } else { 417 lwkt_reltoken(&tty_token); 418 return (EBUSY); 419 } 420 421 /* 422 * We intentionally do not OR flags with SNOOP_OPEN, but set them so 423 * all previous settings (especially SNOOP_OFLOW) will be cleared. 424 */ 425 snp->snp_flags = SNOOP_OPEN; 426 427 snp->snp_buf = kmalloc(SNOOP_MINLEN, M_SNP, M_WAITOK); 428 snp->snp_blen = SNOOP_MINLEN; 429 snp->snp_base = 0; 430 snp->snp_len = 0; 431 432 /* 433 * snp_tty == NULL is for inactive snoop devices. 434 */ 435 snp->snp_tty = NULL; 436 snp->snp_target = NULL; 437 438 LIST_INSERT_HEAD(&snp_sclist, snp, snp_list); 439 lwkt_reltoken(&tty_token); 440 return (0); 441 } 442 443 /* 444 * NOTE: Must be called with tty_token held 445 */ 446 static int 447 snp_detach(struct snoop *snp) 448 { 449 struct tty *tp; 450 451 ASSERT_LWKT_TOKEN_HELD(&tty_token); 452 snp->snp_base = 0; 453 snp->snp_len = 0; 454 455 /* 456 * If line disc. changed we do not touch this pointer, SLIP/PPP will 457 * change it anyway. 458 */ 459 tp = snp->snp_tty; 460 if (tp == NULL) 461 goto detach_notty; 462 463 if (tp && (tp->t_sc == snp) && (tp->t_state & TS_SNOOP) && 464 tp->t_line == snooplinedisc) { 465 tp->t_sc = NULL; 466 tp->t_state &= ~TS_SNOOP; 467 tp->t_line = snp->snp_olddisc; 468 } else 469 kprintf("Snoop: bad attached tty data.\n"); 470 471 snp->snp_tty = NULL; 472 snp->snp_target = NULL; 473 474 detach_notty: 475 KNOTE(&snp->snp_kq.ki_note, 0); 476 if ((snp->snp_flags & SNOOP_OPEN) == 0) 477 kfree(snp, M_SNP); 478 479 return (0); 480 } 481 482 static int 483 snpclose(struct dev_close_args *ap) 484 { 485 cdev_t dev = ap->a_head.a_dev; 486 struct snoop *snp; 487 int ret; 488 489 lwkt_gettoken(&tty_token); 490 snp = dev->si_drv1; 491 snp->snp_blen = 0; 492 LIST_REMOVE(snp, snp_list); 493 kfree(snp->snp_buf, M_SNP); 494 snp->snp_flags &= ~SNOOP_OPEN; 495 dev->si_drv1 = NULL; 496 if (dev->si_uminor >= SNP_PREALLOCATED_UNITS) { 497 devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(snp), dev->si_uminor); 498 destroy_dev(dev); 499 } 500 ret = snp_detach(snp); 501 lwkt_reltoken(&tty_token); 502 return ret; 503 } 504 505 /* 506 * NOTE: Must be called with tty_token held 507 */ 508 static int 509 snp_down(struct snoop *snp) 510 { 511 512 ASSERT_LWKT_TOKEN_HELD(&tty_token); 513 if (snp->snp_blen != SNOOP_MINLEN) { 514 kfree(snp->snp_buf, M_SNP); 515 snp->snp_buf = kmalloc(SNOOP_MINLEN, M_SNP, M_WAITOK); 516 snp->snp_blen = SNOOP_MINLEN; 517 } 518 snp->snp_flags |= SNOOP_DOWN; 519 520 return (snp_detach(snp)); 521 } 522 523 static int 524 snpioctl(struct dev_ioctl_args *ap) 525 { 526 cdev_t dev = ap->a_head.a_dev; 527 struct snoop *snp; 528 struct tty *tp, *tpo; 529 cdev_t tdev; 530 int ret; 531 532 lwkt_gettoken(&tty_token); 533 snp = dev->si_drv1; 534 switch (ap->a_cmd) { 535 case SNPSTTY: 536 tdev = udev2dev(*((udev_t *)ap->a_data), 0); 537 if (tdev == NULL) { 538 lwkt_reltoken(&tty_token); 539 ret = snp_down(snp); 540 return ret; 541 } 542 543 tp = snpdevtotty(tdev); 544 if (!tp) { 545 lwkt_reltoken(&tty_token); 546 return (EINVAL); 547 } 548 if (tp->t_state & TS_SNOOP) { 549 lwkt_reltoken(&tty_token); 550 return (EBUSY); 551 } 552 553 crit_enter(); 554 555 if (snp->snp_target == NULL) { 556 tpo = snp->snp_tty; 557 if (tpo) 558 tpo->t_state &= ~TS_SNOOP; 559 } 560 561 tp->t_sc = (caddr_t)snp; 562 tp->t_state |= TS_SNOOP; 563 snp->snp_olddisc = tp->t_line; 564 tp->t_line = snooplinedisc; 565 snp->snp_tty = tp; 566 snp->snp_target = tdev; 567 568 /* 569 * Clean overflow and down flags - 570 * we'll have a chance to get them in the future :))) 571 */ 572 snp->snp_flags &= ~SNOOP_OFLOW; 573 snp->snp_flags &= ~SNOOP_DOWN; 574 crit_exit(); 575 break; 576 577 case SNPGTTY: 578 /* 579 * We keep snp_target field specially to make 580 * SNPGTTY happy, else we can't know what is device 581 * major/minor for tty. 582 */ 583 *((cdev_t *)ap->a_data) = snp->snp_target; 584 break; 585 586 case FIOASYNC: 587 if (*(int *)ap->a_data) 588 snp->snp_flags |= SNOOP_ASYNC; 589 else 590 snp->snp_flags &= ~SNOOP_ASYNC; 591 break; 592 593 case FIONREAD: 594 crit_enter(); 595 if (snp->snp_tty != NULL) 596 *(int *)ap->a_data = snp->snp_len; 597 else 598 if (snp->snp_flags & SNOOP_DOWN) { 599 if (snp->snp_flags & SNOOP_OFLOW) 600 *(int *)ap->a_data = SNP_OFLOW; 601 else 602 *(int *)ap->a_data = SNP_TTYCLOSE; 603 } else { 604 *(int *)ap->a_data = SNP_DETACH; 605 } 606 crit_exit(); 607 break; 608 609 default: 610 lwkt_reltoken(&tty_token); 611 return (ENOTTY); 612 } 613 lwkt_reltoken(&tty_token); 614 return (0); 615 } 616 617 static struct filterops snpfiltops_rd = 618 { FILTEROP_ISFD, NULL, snpfilter_detach, snpfilter_rd }; 619 static struct filterops snpfiltops_wr = 620 { FILTEROP_ISFD, NULL, snpfilter_detach, snpfilter_wr }; 621 622 static int 623 snpkqfilter(struct dev_kqfilter_args *ap) 624 { 625 cdev_t dev = ap->a_head.a_dev; 626 struct snoop *snp = dev->si_drv1; 627 struct knote *kn = ap->a_kn; 628 struct klist *klist; 629 630 lwkt_gettoken(&tty_token); 631 ap->a_result = 0; 632 633 switch (kn->kn_filter) { 634 case EVFILT_READ: 635 kn->kn_fop = &snpfiltops_rd; 636 kn->kn_hook = (caddr_t)snp; 637 break; 638 case EVFILT_WRITE: 639 kn->kn_fop = &snpfiltops_wr; 640 kn->kn_hook = (caddr_t)snp; 641 break; 642 default: 643 ap->a_result = EOPNOTSUPP; 644 lwkt_reltoken(&tty_token); 645 return (0); 646 } 647 648 klist = &snp->snp_kq.ki_note; 649 knote_insert(klist, kn); 650 651 lwkt_reltoken(&tty_token); 652 return (0); 653 } 654 655 static void 656 snpfilter_detach(struct knote *kn) 657 { 658 struct snoop *snp = (struct snoop *)kn->kn_hook; 659 struct klist *klist; 660 661 klist = &snp->snp_kq.ki_note; 662 knote_insert(klist, kn); 663 } 664 665 static int 666 snpfilter_rd(struct knote *kn, long hint) 667 { 668 struct snoop *snp = (struct snoop *)kn->kn_hook; 669 int ready = 0; 670 671 lwkt_gettoken(&tty_token); 672 /* 673 * If snoop is down, we don't want to poll forever so we return 1. 674 * Caller should see if we down via FIONREAD ioctl(). The last should 675 * return -1 to indicate down state. 676 */ 677 if (snp->snp_flags & SNOOP_DOWN || snp->snp_len > 0) 678 ready = 1; 679 680 lwkt_reltoken(&tty_token); 681 return (ready); 682 } 683 684 static int 685 snpfilter_wr(struct knote *kn, long hint) 686 { 687 /* Writing is always OK */ 688 return (1); 689 } 690 691 static int 692 snpclone(struct dev_clone_args *ap) 693 { 694 int unit; 695 lwkt_gettoken(&tty_token); 696 unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(snp), 0); 697 ap->a_dev = make_only_dev(&snp_ops, unit, UID_ROOT, GID_WHEEL, 0600, 698 "snp%d", unit); 699 lwkt_reltoken(&tty_token); 700 return 0; 701 } 702 703 static int 704 snp_modevent(module_t mod, int type, void *data) 705 { 706 int i; 707 708 lwkt_gettoken(&tty_token); 709 switch (type) { 710 case MOD_LOAD: 711 snooplinedisc = ldisc_register(LDISC_LOAD, &snpdisc); 712 make_autoclone_dev(&snp_ops, &DEVFS_CLONE_BITMAP(snp), 713 snpclone, UID_ROOT, GID_WHEEL, 0600, "snp"); 714 715 for (i = 0; i < SNP_PREALLOCATED_UNITS; i++) { 716 make_dev(&snp_ops, i, UID_ROOT, GID_WHEEL, 0600, "snp%d", i); 717 devfs_clone_bitmap_set(&DEVFS_CLONE_BITMAP(snp), i); 718 } 719 break; 720 case MOD_UNLOAD: 721 if (!LIST_EMPTY(&snp_sclist)) 722 return (EBUSY); 723 ldisc_deregister(snooplinedisc); 724 devfs_clone_handler_del("snp"); 725 dev_ops_remove_all(&snp_ops); 726 devfs_clone_bitmap_uninit(&DEVFS_CLONE_BITMAP(snp)); 727 break; 728 default: 729 break; 730 } 731 lwkt_reltoken(&tty_token); 732 return (0); 733 } 734 735 static moduledata_t snp_mod = { 736 "snp", 737 snp_modevent, 738 NULL 739 }; 740 DECLARE_MODULE(snp, snp_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); 741