6324 Add an `ndp' tool for manipulating the neighbors table
[illumos-gate.git] / usr / src / uts / common / io / sysevent.c
blob69065b9506397c9dbeaae5f45f7e2601fffde374
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
22 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
27 * Sysevent Driver for GPEC
30 #include <sys/types.h>
31 #include <sys/param.h>
32 #include <sys/cred.h>
33 #include <sys/file.h>
34 #include <sys/stat.h>
35 #include <sys/conf.h>
36 #include <sys/ddi.h>
37 #include <sys/sunddi.h>
38 #include <sys/modctl.h>
39 #include <sys/open.h> /* OTYP_CHR definition */
40 #include <sys/sysmacros.h> /* L_BITSMINOR definition */
41 #include <sys/bitmap.h>
42 #include <sys/sysevent.h>
43 #include <sys/sysevent_impl.h>
45 static dev_info_t *sysevent_devi;
47 /* Definitions for binding handle array */
48 static ulong_t sysevent_bitmap_initial = 1; /* index 0 indicates error */
49 static ulong_t *sysevent_minor_bitmap = &sysevent_bitmap_initial;
50 static size_t sysevent_minor_bits = BT_NBIPUL;
51 static kmutex_t sysevent_minor_mutex;
54 * evchan_ctl acts as a container for the binding handle
56 typedef struct evchan_ctl {
57 evchan_t *chp;
58 } evchan_ctl_t;
60 static void *evchan_ctlp;
63 * Check if it's a null terminated array - to avoid DoS attack
64 * It is supposed that string points to an array with
65 * a minimum length of len. len must be strlen + 1.
66 * Checks for printable characters are already done in library.
68 static int
69 sysevent_isstrend(char *string, size_t len)
71 /* Return 0 if string has length of zero */
72 if (len > 0) {
73 return (string[len - 1] == '\0' ? 1 : 0);
74 } else {
75 return (0);
80 * Following sysevent_minor_* routines map
81 * a binding handle (evchan_t *) to a minor number
82 * Has to be called w/ locks held.
84 static ulong_t *
85 sysevent_minor_alloc(void)
87 ulong_t *bhst = sysevent_minor_bitmap;
89 /* Increase bitmap by one BT_NBIPUL */
90 if (sysevent_minor_bits + BT_NBIPUL > SYSEVENT_MINOR_MAX) {
91 return ((ulong_t *)NULL);
93 sysevent_minor_bitmap = kmem_zalloc(
94 BT_SIZEOFMAP(sysevent_minor_bits + BT_NBIPUL), KM_SLEEP);
95 bcopy(bhst, sysevent_minor_bitmap, BT_SIZEOFMAP(sysevent_minor_bits));
96 if (bhst != &sysevent_bitmap_initial)
97 kmem_free(bhst, BT_SIZEOFMAP(sysevent_minor_bits));
98 sysevent_minor_bits += BT_NBIPUL;
100 return (sysevent_minor_bitmap);
103 static void
104 sysevent_minor_free(ulong_t *bitmap)
106 if (bitmap != &sysevent_bitmap_initial)
107 kmem_free(bitmap, BT_SIZEOFMAP(sysevent_minor_bits));
110 static index_t
111 sysevent_minor_get(void)
113 index_t idx;
114 ulong_t *bhst;
116 /* Search for an available index */
117 mutex_enter(&sysevent_minor_mutex);
118 if ((idx = bt_availbit(sysevent_minor_bitmap,
119 sysevent_minor_bits)) == -1) {
120 /* All busy - allocate additional binding handle bitmap space */
121 if ((bhst = sysevent_minor_alloc()) == NULL) {
122 /* Reached our maximum of id's == SHRT_MAX */
123 mutex_exit(&sysevent_minor_mutex);
124 return (0);
125 } else {
126 sysevent_minor_bitmap = bhst;
128 idx = bt_availbit(sysevent_minor_bitmap, sysevent_minor_bits);
130 BT_SET(sysevent_minor_bitmap, idx);
131 mutex_exit(&sysevent_minor_mutex);
132 return (idx);
135 static void
136 sysevent_minor_rele(index_t idx)
138 mutex_enter(&sysevent_minor_mutex);
139 ASSERT(BT_TEST(sysevent_minor_bitmap, idx) == 1);
140 BT_CLEAR(sysevent_minor_bitmap, idx);
141 mutex_exit(&sysevent_minor_mutex);
144 static void
145 sysevent_minor_init(void)
147 mutex_init(&sysevent_minor_mutex, NULL, MUTEX_DEFAULT, NULL);
150 /* ARGSUSED */
151 static int
152 sysevent_publish(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
154 int km_flags;
155 sev_publish_args_t uargs;
156 sysevent_impl_t *ev;
157 evchan_ctl_t *ctl;
159 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
160 if (ctl == NULL || ctl->chp == NULL)
161 return (ENXIO);
163 if (copyin(arg, &uargs, sizeof (sev_publish_args_t)) != 0)
164 return (EFAULT);
167 * This limits the size of an event
169 if (uargs.ev.len > MAX_EV_SIZE_LEN)
170 return (EOVERFLOW);
173 * Check for valid uargs.flags
175 if (uargs.flags & ~(EVCH_NOSLEEP | EVCH_SLEEP | EVCH_QWAIT))
176 return (EINVAL);
179 * Check that at least one of EVCH_NOSLEEP or EVCH_SLEEP is
180 * specified
182 km_flags = uargs.flags & (EVCH_NOSLEEP | EVCH_SLEEP);
183 if (km_flags != EVCH_NOSLEEP && km_flags != EVCH_SLEEP)
184 return (EINVAL);
186 ev = evch_usrallocev(uargs.ev.len, uargs.flags);
188 if (copyin((void *)(uintptr_t)uargs.ev.name, ev, uargs.ev.len) != 0) {
189 evch_usrfreeev(ev);
190 return (EFAULT);
193 return (evch_usrpostevent(ctl->chp, ev, uargs.flags));
195 /* Event will be freed internally */
199 * sysevent_chan_open - used to open a channel in the GPEC channel layer
202 /* ARGSUSED */
203 static int
204 sysevent_chan_open(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
206 sev_bind_args_t uargs;
207 evchan_ctl_t *ctl;
208 char *chan_name;
209 int ec;
211 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
212 if (ctl == NULL) {
213 return (ENXIO);
216 if (copyin(arg, &uargs, sizeof (sev_bind_args_t)) != 0)
217 return (EFAULT);
219 if (uargs.chan_name.len > MAX_CHNAME_LEN)
220 return (EINVAL);
222 chan_name = kmem_alloc(uargs.chan_name.len, KM_SLEEP);
224 if (copyin((void *)(uintptr_t)uargs.chan_name.name, chan_name,
225 uargs.chan_name.len) != 0) {
226 kmem_free(chan_name, uargs.chan_name.len);
227 return (EFAULT);
230 if (!sysevent_isstrend(chan_name, uargs.chan_name.len)) {
231 kmem_free(chan_name, uargs.chan_name.len);
232 return (EINVAL);
236 * Check of uargs.flags and uargs.perms just to avoid DoS attacks.
237 * libsysevent does this carefully
239 ctl->chp = evch_usrchanopen((const char *)chan_name,
240 uargs.flags & EVCH_B_FLAGS, &ec);
242 kmem_free(chan_name, uargs.chan_name.len);
244 if (ec != 0) {
245 return (ec);
248 return (0);
251 /* ARGSUSED */
252 static int
253 sysevent_chan_control(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
255 sev_control_args_t uargs;
256 evchan_ctl_t *ctl;
257 int rc;
259 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
260 if (ctl == NULL || ctl->chp == NULL)
261 return (ENXIO);
263 if (copyin(arg, &uargs, sizeof (sev_control_args_t)) != 0)
264 return (EFAULT);
266 switch (uargs.cmd) {
267 case EVCH_GET_CHAN_LEN:
268 case EVCH_GET_CHAN_LEN_MAX:
269 rc = evch_usrcontrol_get(ctl->chp, uargs.cmd, &uargs.value);
270 if (rc == 0) {
271 if (copyout((void *)&uargs, arg,
272 sizeof (sev_control_args_t)) != 0) {
273 rc = EFAULT;
276 break;
277 case EVCH_SET_CHAN_LEN:
278 rc = evch_usrcontrol_set(ctl->chp, uargs.cmd, uargs.value);
279 break;
280 default:
281 rc = EINVAL;
283 return (rc);
286 /* ARGSUSED */
287 static int
288 sysevent_subscribe(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
290 sev_subscribe_args_t uargs;
291 char *sid;
292 char *class_info = NULL;
293 evchan_ctl_t *ctl;
294 int rc;
296 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
297 if (ctl == NULL || ctl->chp == NULL)
298 return (ENXIO);
300 if (copyin(arg, &uargs, sizeof (sev_subscribe_args_t)) != 0)
301 return (EFAULT);
303 if (uargs.sid.len > MAX_SUBID_LEN ||
304 uargs.class_info.len > MAX_CLASS_LEN)
305 return (EINVAL);
307 sid = kmem_alloc(uargs.sid.len, KM_SLEEP);
308 if (copyin((void *)(uintptr_t)uargs.sid.name,
309 sid, uargs.sid.len) != 0) {
310 kmem_free(sid, uargs.sid.len);
311 return (EFAULT);
313 if (!sysevent_isstrend(sid, uargs.sid.len)) {
314 kmem_free(sid, uargs.sid.len);
315 return (EINVAL);
318 /* If class string empty then class EC_ALL is assumed */
319 if (uargs.class_info.len != 0) {
320 class_info = kmem_alloc(uargs.class_info.len, KM_SLEEP);
321 if (copyin((void *)(uintptr_t)uargs.class_info.name, class_info,
322 uargs.class_info.len) != 0) {
323 kmem_free(class_info, uargs.class_info.len);
324 kmem_free(sid, uargs.sid.len);
325 return (EFAULT);
327 if (!sysevent_isstrend(class_info, uargs.class_info.len)) {
328 kmem_free(class_info, uargs.class_info.len);
329 kmem_free(sid, uargs.sid.len);
330 return (EINVAL);
335 * Check of uargs.flags just to avoid DoS attacks
336 * libsysevent does this carefully.
338 rc = evch_usrsubscribe(ctl->chp, sid, class_info,
339 (int)uargs.door_desc, uargs.flags);
341 kmem_free(class_info, uargs.class_info.len);
342 kmem_free(sid, uargs.sid.len);
344 return (rc);
347 /* ARGSUSED */
348 static int
349 sysevent_unsubscribe(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
351 sev_unsubscribe_args_t uargs;
352 char *sid;
353 evchan_ctl_t *ctl;
355 if (copyin(arg, &uargs, sizeof (sev_unsubscribe_args_t)) != 0)
356 return (EFAULT);
358 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
359 if (ctl == NULL || ctl->chp == NULL)
360 return (ENXIO);
362 if (uargs.sid.len > MAX_SUBID_LEN)
363 return (EINVAL);
365 /* Unsubscribe for all */
366 if (uargs.sid.len == 0) {
367 evch_usrunsubscribe(ctl->chp, NULL, 0);
368 return (0);
371 sid = kmem_alloc(uargs.sid.len, KM_SLEEP);
373 if (copyin((void *)(uintptr_t)uargs.sid.name,
374 sid, uargs.sid.len) != 0) {
375 kmem_free(sid, uargs.sid.len);
376 return (EFAULT);
379 evch_usrunsubscribe(ctl->chp, sid, 0);
381 kmem_free(sid, uargs.sid.len);
383 return (0);
386 /* ARGSUSED */
387 static int
388 sysevent_channames(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
390 sev_chandata_args_t uargs;
391 char *buf;
392 int len;
393 int rc = 0;
395 if (copyin(arg, &uargs, sizeof (sev_chandata_args_t)) != 0)
396 return (EFAULT);
398 if (uargs.out_data.len == 0 || uargs.out_data.len > EVCH_MAX_DATA_SIZE)
399 return (EINVAL);
401 buf = kmem_alloc(uargs.out_data.len, KM_SLEEP);
403 if ((len = evch_usrgetchnames(buf, uargs.out_data.len)) == -1) {
404 rc = EOVERFLOW;
407 if (rc == 0) {
408 ASSERT(len <= uargs.out_data.len);
409 if (copyout(buf,
410 (void *)(uintptr_t)uargs.out_data.name, len) != 0) {
411 rc = EFAULT;
415 kmem_free(buf, uargs.out_data.len);
417 return (rc);
420 /* ARGSUSED */
421 static int
422 sysevent_chandata(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
424 sev_chandata_args_t uargs;
425 char *channel;
426 char *buf;
427 int len;
428 int rc = 0;
430 if (copyin(arg, &uargs, sizeof (sev_chandata_args_t)) != 0)
431 return (EFAULT);
433 if (uargs.in_data.len > MAX_CHNAME_LEN ||
434 uargs.out_data.len > EVCH_MAX_DATA_SIZE)
435 return (EINVAL);
437 channel = kmem_alloc(uargs.in_data.len, KM_SLEEP);
439 if (copyin((void *)(uintptr_t)uargs.in_data.name, channel,
440 uargs.in_data.len) != 0) {
441 kmem_free(channel, uargs.in_data.len);
442 return (EFAULT);
445 if (!sysevent_isstrend(channel, uargs.in_data.len)) {
446 kmem_free(channel, uargs.in_data.len);
447 return (EINVAL);
450 buf = kmem_alloc(uargs.out_data.len, KM_SLEEP);
452 len = evch_usrgetchdata(channel, buf, uargs.out_data.len);
453 if (len == 0) {
454 rc = EOVERFLOW;
455 } else if (len == -1) {
456 rc = ENOENT;
459 if (rc == 0) {
460 ASSERT(len <= uargs.out_data.len);
461 if (copyout(buf,
462 (void *)(uintptr_t)uargs.out_data.name, len) != 0) {
463 rc = EFAULT;
467 kmem_free(buf, uargs.out_data.len);
468 kmem_free(channel, uargs.in_data.len);
470 return (rc);
473 /* ARGSUSED */
474 static int
475 sysevent_setpropnvl(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
477 sev_propnvl_args_t uargs;
478 nvlist_t *nvl = NULL;
479 evchan_ctl_t *ctl;
480 size_t bufsz;
481 char *buf;
483 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
484 if (ctl == NULL || ctl->chp == NULL)
485 return (ENXIO);
487 if (copyin(arg, &uargs, sizeof (uargs)) != 0)
488 return (EFAULT);
490 if (uargs.packednvl.name != 0) {
491 bufsz = uargs.packednvl.len;
493 if (bufsz == 0)
494 return (EINVAL);
496 if (bufsz > EVCH_MAX_DATA_SIZE)
497 return (EOVERFLOW);
499 buf = kmem_alloc(bufsz, KM_SLEEP);
501 if (copyin((void *)(uintptr_t)uargs.packednvl.name, buf,
502 bufsz) != 0 ||
503 nvlist_unpack(buf, bufsz, &nvl, KM_SLEEP) != 0) {
504 kmem_free(buf, bufsz);
505 return (EFAULT);
508 kmem_free(buf, bufsz);
510 if (nvl == NULL)
511 return (EINVAL);
514 evch_usrsetpropnvl(ctl->chp, nvl);
515 return (0);
518 /* ARGSUSED */
519 static int
520 sysevent_getpropnvl(dev_t dev, int *rvalp, void *arg, int flag, cred_t *cr)
522 sev_propnvl_args_t uargs;
523 size_t reqsz, avlsz;
524 evchan_ctl_t *ctl;
525 nvlist_t *nvl;
526 int64_t gen;
527 int rc;
529 ctl = ddi_get_soft_state(evchan_ctlp, getminor(dev));
531 if (ctl == NULL || ctl->chp == NULL)
532 return (ENXIO);
534 if (copyin(arg, &uargs, sizeof (uargs)) != 0)
535 return (EFAULT);
537 if ((rc = evch_usrgetpropnvl(ctl->chp, &nvl, &gen)) != 0)
538 return (rc);
540 if (nvl != NULL) {
541 avlsz = uargs.packednvl.len;
543 if (nvlist_size(nvl, &reqsz, NV_ENCODE_NATIVE) != 0) {
544 nvlist_free(nvl);
545 return (EINVAL);
548 if (reqsz > EVCH_MAX_DATA_SIZE) {
549 nvlist_free(nvl);
550 return (E2BIG);
553 if (reqsz <= avlsz) {
554 char *buf = kmem_alloc(reqsz, KM_SLEEP);
556 if (nvlist_pack(nvl, &buf, &reqsz,
557 NV_ENCODE_NATIVE, 0) != 0 || copyout(buf,
558 (void *)(uintptr_t)uargs.packednvl.name,
559 reqsz) != 0) {
560 kmem_free(buf, reqsz);
561 nvlist_free(nvl);
562 return (EFAULT);
564 kmem_free(buf, reqsz);
565 rc = 0;
566 } else {
567 rc = EOVERFLOW;
569 uargs.packednvl.len = (uint32_t)reqsz;
570 nvlist_free(nvl);
571 } else {
572 uargs.packednvl.len = 0;
573 rc = 0;
576 uargs.generation = gen;
577 if (copyout((void *)&uargs, arg, sizeof (uargs)) != 0)
578 rc = EFAULT;
580 return (rc);
583 /*ARGSUSED*/
584 static int
585 sysevent_ioctl(dev_t dev, int cmd, intptr_t arg,
586 int flag, cred_t *cr, int *rvalp)
588 int rc;
590 switch (cmd) {
591 case SEV_PUBLISH:
592 rc = sysevent_publish(dev, rvalp, (void *)arg, flag, cr);
593 break;
594 case SEV_CHAN_OPEN:
595 rc = sysevent_chan_open(dev, rvalp, (void *)arg, flag, cr);
596 break;
597 case SEV_CHAN_CONTROL:
598 rc = sysevent_chan_control(dev, rvalp, (void *)arg, flag, cr);
599 break;
600 case SEV_SUBSCRIBE:
601 rc = sysevent_subscribe(dev, rvalp, (void *)arg, flag, cr);
602 break;
603 case SEV_UNSUBSCRIBE:
604 rc = sysevent_unsubscribe(dev, rvalp, (void *)arg, flag, cr);
605 break;
606 case SEV_CHANNAMES:
607 rc = sysevent_channames(dev, rvalp, (void *)arg, flag, cr);
608 break;
609 case SEV_CHANDATA:
610 rc = sysevent_chandata(dev, rvalp, (void *)arg, flag, cr);
611 break;
612 case SEV_SETPROPNVL:
613 rc = sysevent_setpropnvl(dev, rvalp, (void *)arg, flag, cr);
614 break;
615 case SEV_GETPROPNVL:
616 rc = sysevent_getpropnvl(dev, rvalp, (void *)arg, flag, cr);
617 break;
618 default:
619 rc = EINVAL;
622 return (rc);
625 /*ARGSUSED*/
626 static int
627 sysevent_open(dev_t *devp, int flag, int otyp, cred_t *cr)
629 int minor;
631 if (otyp != OTYP_CHR)
632 return (EINVAL);
634 if (getminor(*devp) != 0)
635 return (ENXIO);
637 minor = sysevent_minor_get();
638 if (minor == 0)
639 /* All minors are busy */
640 return (EBUSY);
642 if (ddi_soft_state_zalloc(evchan_ctlp, minor)
643 != DDI_SUCCESS) {
644 sysevent_minor_rele(minor);
645 return (ENOMEM);
648 *devp = makedevice(getmajor(*devp), minor);
650 return (0);
653 /*ARGSUSED*/
654 static int
655 sysevent_close(dev_t dev, int flag, int otyp, cred_t *cr)
657 int minor = (int)getminor(dev);
658 evchan_ctl_t *ctl;
660 if (otyp != OTYP_CHR)
661 return (EINVAL);
663 ctl = ddi_get_soft_state(evchan_ctlp, minor);
664 if (ctl == NULL) {
665 return (ENXIO);
668 if (ctl->chp) {
669 /* Release all non-persistant subscriptions */
670 evch_usrunsubscribe(ctl->chp, NULL, EVCH_SUB_KEEP);
671 evch_usrchanclose(ctl->chp);
674 ddi_soft_state_free(evchan_ctlp, minor);
675 sysevent_minor_rele(minor);
677 return (0);
680 /* ARGSUSED */
681 static int
682 sysevent_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
683 void *arg, void **result)
685 switch (infocmd) {
686 case DDI_INFO_DEVT2DEVINFO:
687 *result = sysevent_devi;
688 return (DDI_SUCCESS);
689 case DDI_INFO_DEVT2INSTANCE:
690 *result = 0;
691 return (DDI_SUCCESS);
693 return (DDI_FAILURE);
696 /* ARGSUSED */
697 static int
698 sysevent_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
701 if (cmd != DDI_ATTACH) {
702 return (DDI_FAILURE);
705 if (ddi_create_minor_node(devi, "sysevent", S_IFCHR,
706 0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
707 ddi_remove_minor_node(devi, NULL);
708 return (DDI_FAILURE);
710 sysevent_devi = devi;
712 sysevent_minor_init();
714 return (DDI_SUCCESS);
717 static int
718 sysevent_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
720 if (cmd != DDI_DETACH) {
721 return (DDI_FAILURE);
724 sysevent_minor_free(sysevent_minor_bitmap);
725 ddi_remove_minor_node(devi, NULL);
726 return (DDI_SUCCESS);
729 static struct cb_ops sysevent_cb_ops = {
730 sysevent_open, /* open */
731 sysevent_close, /* close */
732 nodev, /* strategy */
733 nodev, /* print */
734 nodev, /* dump */
735 nodev, /* read */
736 nodev, /* write */
737 sysevent_ioctl, /* ioctl */
738 nodev, /* devmap */
739 nodev, /* mmap */
740 nodev, /* segmap */
741 nochpoll, /* poll */
742 ddi_prop_op, /* prop_op */
743 0, /* streamtab */
744 D_NEW|D_MP, /* flag */
745 NULL, /* aread */
746 NULL /* awrite */
749 static struct dev_ops sysevent_ops = {
750 DEVO_REV, /* devo_rev */
751 0, /* refcnt */
752 sysevent_info, /* info */
753 nulldev, /* identify */
754 nulldev, /* probe */
755 sysevent_attach, /* attach */
756 sysevent_detach, /* detach */
757 nodev, /* reset */
758 &sysevent_cb_ops, /* driver operations */
759 (struct bus_ops *)0, /* no bus operations */
760 nulldev, /* power */
761 ddi_quiesce_not_needed, /* quiesce */
764 static struct modldrv modldrv = {
765 &mod_driverops, "sysevent driver", &sysevent_ops
768 static struct modlinkage modlinkage = {
769 MODREV_1, &modldrv, NULL
773 _init(void)
775 int s;
777 s = ddi_soft_state_init(&evchan_ctlp, sizeof (evchan_ctl_t), 1);
778 if (s != 0)
779 return (s);
781 if ((s = mod_install(&modlinkage)) != 0)
782 ddi_soft_state_fini(&evchan_ctlp);
783 return (s);
787 _fini(void)
789 int s;
791 if ((s = mod_remove(&modlinkage)) != 0)
792 return (s);
794 ddi_soft_state_fini(&evchan_ctlp);
795 return (s);
799 _info(struct modinfo *modinfop)
801 return (mod_info(&modlinkage, modinfop));