evtchn_dev.c revision 7656:2621e50fdf4a
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 (the "License").
6 * You may not use this file except in compliance with the License.
7 *
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.
12 *
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]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26
27
28/*
29 * evtchn.c
30 *
31 * Driver for receiving and demuxing event-channel signals.
32 *
33 * Copyright (c) 2004-2005, K A Fraser
34 * Multi-process extensions Copyright (c) 2004, Steven Smith
35 *
36 * This file may be distributed separately from the Linux kernel, or
37 * incorporated into other software packages, subject to the following license:
38 *
39 * Permission is hereby granted, free of charge, to any person obtaining a copy
40 * of this source file (the "Software"), to deal in the Software without
41 * restriction, including without limitation the rights to use, copy, modify,
42 * merge, publish, distribute, sublicense, and/or sell copies of the Software,
43 * and to permit persons to whom the Software is furnished to do so, subject to
44 * the following conditions:
45 *
46 * The above copyright notice and this permission notice shall be included in
47 * all copies or substantial portions of the Software.
48 *
49 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
50 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
51 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
52 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
53 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
54 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
55 * IN THE SOFTWARE.
56 */
57
58#include <sys/types.h>
59#include <sys/hypervisor.h>
60#include <sys/machsystm.h>
61#include <sys/mutex.h>
62#include <sys/evtchn_impl.h>
63#include <sys/ddi_impldefs.h>
64#include <sys/avintr.h>
65#include <sys/cpuvar.h>
66#include <sys/smp_impldefs.h>
67#include <sys/archsystm.h>
68#include <sys/sysmacros.h>
69#include <sys/fcntl.h>
70#include <sys/open.h>
71#include <sys/stat.h>
72#include <sys/psm.h>
73#include <sys/cpu.h>
74#include <sys/cmn_err.h>
75#include <sys/xen_errno.h>
76#include <sys/policy.h>
77#include <xen/sys/evtchn.h>
78
79/* Some handy macros */
80#define	EVTCHNDRV_MINOR2INST(minor)	((int)(minor))
81#define	EVTCHNDRV_DEFAULT_NCLONES 	256
82#define	EVTCHNDRV_INST2SOFTS(inst)	\
83	(ddi_get_soft_state(evtchndrv_statep, (inst)))
84
85/* Soft state data structure for evtchn driver */
86struct evtsoftdata {
87	dev_info_t *dip;
88	/* Notification ring, accessed via /dev/xen/evtchn. */
89#define	EVTCHN_RING_SIZE	(PAGESIZE / sizeof (evtchn_port_t))
90#define	EVTCHN_RING_MASK(_i)	((_i) & (EVTCHN_RING_SIZE - 1))
91	evtchn_port_t *ring;
92	unsigned int ring_cons, ring_prod, ring_overflow;
93
94	/* Processes wait on this queue when ring is empty. */
95	kcondvar_t evtchn_wait;
96	kmutex_t evtchn_lock;
97	struct pollhead evtchn_pollhead;
98
99	/* last pid to bind to this event channel. debug aid. */
100	pid_t pid;
101};
102
103static void *evtchndrv_statep;
104int evtchndrv_nclones = EVTCHNDRV_DEFAULT_NCLONES;
105static int *evtchndrv_clone_tab;
106static dev_info_t *evtchndrv_dip;
107static kmutex_t evtchndrv_clone_tab_mutex;
108
109static int evtchndrv_detach(dev_info_t *, ddi_detach_cmd_t);
110
111/* Who's bound to each port? */
112static struct evtsoftdata *port_user[NR_EVENT_CHANNELS];
113static kmutex_t port_user_lock;
114
115void
116evtchn_device_upcall()
117{
118	struct evtsoftdata *ep;
119	int port;
120
121	/*
122	 * This is quite gross, we had to leave the evtchn that led to this
123	 * invocation in a global mailbox, retrieve it now.
124	 * We do this because the interface doesn't offer us a way to pass
125	 * a dynamic argument up through the generic interrupt service layer.
126	 * The mailbox is safe since we either run with interrupts disabled or
127	 * non-preemptable till we reach here.
128	 */
129	port = ec_dev_mbox;
130	ASSERT(port != 0);
131	ec_dev_mbox = 0;
132	ec_clear_evtchn(port);
133	mutex_enter(&port_user_lock);
134
135	if ((ep = port_user[port]) != NULL) {
136		mutex_enter(&ep->evtchn_lock);
137		if ((ep->ring_prod - ep->ring_cons) < EVTCHN_RING_SIZE) {
138			ep->ring[EVTCHN_RING_MASK(ep->ring_prod)] = port;
139			/*
140			 * Wake up reader when ring goes non-empty
141			 */
142			if (ep->ring_cons == ep->ring_prod++) {
143				cv_signal(&ep->evtchn_wait);
144				mutex_exit(&ep->evtchn_lock);
145				pollwakeup(&ep->evtchn_pollhead,
146				    POLLIN | POLLRDNORM);
147				goto done;
148			}
149		} else {
150			ep->ring_overflow = 1;
151		}
152		mutex_exit(&ep->evtchn_lock);
153	}
154
155done:
156	mutex_exit(&port_user_lock);
157}
158
159/* ARGSUSED */
160static int
161evtchndrv_read(dev_t dev, struct uio *uio, cred_t *cr)
162{
163	int rc = 0;
164	ssize_t count;
165	unsigned int c, p, bytes1 = 0, bytes2 = 0;
166	struct evtsoftdata *ep;
167	minor_t minor = getminor(dev);
168
169	if (secpolicy_xvm_control(cr))
170		return (EPERM);
171
172	ep = EVTCHNDRV_INST2SOFTS(EVTCHNDRV_MINOR2INST(minor));
173
174	/* Whole number of ports. */
175	count = uio->uio_resid;
176	count &= ~(sizeof (evtchn_port_t) - 1);
177
178	if (count == 0)
179		return (0);
180
181	if (count > PAGESIZE)
182		count = PAGESIZE;
183
184	mutex_enter(&ep->evtchn_lock);
185	for (;;) {
186		if (ep->ring_overflow) {
187			rc = EFBIG;
188			goto done;
189		}
190
191		if ((c = ep->ring_cons) != (p = ep->ring_prod))
192			break;
193
194		if (uio->uio_fmode & O_NONBLOCK) {
195			rc = EAGAIN;
196			goto done;
197		}
198
199		if (cv_wait_sig(&ep->evtchn_wait, &ep->evtchn_lock) == 0) {
200			rc = EINTR;
201			goto done;
202		}
203	}
204
205	/* Byte lengths of two chunks. Chunk split (if any) is at ring wrap. */
206	if (((c ^ p) & EVTCHN_RING_SIZE) != 0) {
207		bytes1 = (EVTCHN_RING_SIZE - EVTCHN_RING_MASK(c)) *
208		    sizeof (evtchn_port_t);
209		bytes2 = EVTCHN_RING_MASK(p) * sizeof (evtchn_port_t);
210	} else {
211		bytes1 = (p - c) * sizeof (evtchn_port_t);
212		bytes2 = 0;
213	}
214
215	/* Truncate chunks according to caller's maximum byte count. */
216	if (bytes1 > count) {
217		bytes1 = count;
218		bytes2 = 0;
219	} else if ((bytes1 + bytes2) > count) {
220		bytes2 = count - bytes1;
221	}
222
223	if (uiomove(&ep->ring[EVTCHN_RING_MASK(c)], bytes1, UIO_READ, uio) ||
224	    ((bytes2 != 0) && uiomove(&ep->ring[0], bytes2, UIO_READ, uio))) {
225		rc = EFAULT;
226		goto done;
227	}
228
229	ep->ring_cons += (bytes1 + bytes2) / sizeof (evtchn_port_t);
230done:
231	mutex_exit(&ep->evtchn_lock);
232	return (rc);
233}
234
235/* ARGSUSED */
236static int
237evtchndrv_write(dev_t dev, struct uio *uio, cred_t *cr)
238{
239	int  rc, i;
240	ssize_t count;
241	evtchn_port_t *kbuf;
242	struct evtsoftdata *ep;
243	ulong_t flags;
244	minor_t minor = getminor(dev);
245
246	if (secpolicy_xvm_control(cr))
247		return (EPERM);
248
249	ep = EVTCHNDRV_INST2SOFTS(EVTCHNDRV_MINOR2INST(minor));
250
251	kbuf = kmem_alloc(PAGESIZE, KM_SLEEP);
252
253	/* Whole number of ports. */
254	count = uio->uio_resid;
255	count &= ~(sizeof (evtchn_port_t) - 1);
256
257	if (count == 0) {
258		rc = 0;
259		goto out;
260	}
261
262	if (count > PAGESIZE)
263		count = PAGESIZE;
264
265	if ((rc = uiomove(kbuf, count, UIO_WRITE, uio)) != 0)
266		goto out;
267
268	mutex_enter(&port_user_lock);
269	for (i = 0; i < (count / sizeof (evtchn_port_t)); i++)
270		if ((kbuf[i] < NR_EVENT_CHANNELS) &&
271		    (port_user[kbuf[i]] == ep)) {
272			flags = intr_clear();
273			ec_unmask_evtchn(kbuf[i]);
274			intr_restore(flags);
275		}
276	mutex_exit(&port_user_lock);
277
278out:
279	kmem_free(kbuf, PAGESIZE);
280	return (rc);
281}
282
283static void
284evtchn_bind_to_user(struct evtsoftdata *u, int port)
285{
286	ulong_t flags;
287
288	/*
289	 * save away the PID of the last process to bind to this event channel.
290	 * Useful for debugging.
291	 */
292	u->pid = ddi_get_pid();
293
294	mutex_enter(&port_user_lock);
295	ASSERT(port_user[port] == NULL);
296	port_user[port] = u;
297	ec_irq_add_evtchn(ec_dev_irq, port);
298	flags = intr_clear();
299	ec_unmask_evtchn(port);
300	intr_restore(flags);
301	mutex_exit(&port_user_lock);
302}
303
304static void
305evtchndrv_close_evtchn(int port)
306{
307	struct evtsoftdata *ep;
308
309	ASSERT(MUTEX_HELD(&port_user_lock));
310	ep = port_user[port];
311	ASSERT(ep != NULL);
312	(void) ec_mask_evtchn(port);
313	/*
314	 * It is possible the event is in transit to us.
315	 * If it is already in the ring buffer, then a client may
316	 * get a spurious event notification on the next read of
317	 * of the evtchn device.  Clients will need to be able to
318	 * handle getting a spurious event notification.
319	 */
320	port_user[port] = NULL;
321	/*
322	 * The event is masked and should stay so, clean it up.
323	 */
324	ec_irq_rm_evtchn(ec_dev_irq, port);
325}
326
327/* ARGSUSED */
328static int
329evtchndrv_ioctl(dev_t dev, int cmd, intptr_t data, int flag, cred_t *cr,
330    int *rvalp)
331{
332	int err = 0;
333	struct evtsoftdata *ep;
334	minor_t minor = getminor(dev);
335
336	if (secpolicy_xvm_control(cr))
337		return (EPERM);
338
339	ep = EVTCHNDRV_INST2SOFTS(EVTCHNDRV_MINOR2INST(minor));
340
341	*rvalp = 0;
342
343	switch (cmd) {
344	case IOCTL_EVTCHN_BIND_VIRQ: {
345		struct ioctl_evtchn_bind_virq bind;
346
347		if (copyin((void *)data, &bind, sizeof (bind))) {
348			err = EFAULT;
349			break;
350		}
351
352		if ((err = xen_bind_virq(bind.virq, 0, rvalp)) != 0)
353			break;
354
355		evtchn_bind_to_user(ep, *rvalp);
356		break;
357	}
358
359	case IOCTL_EVTCHN_BIND_INTERDOMAIN: {
360		struct ioctl_evtchn_bind_interdomain bind;
361
362		if (copyin((void *)data, &bind, sizeof (bind))) {
363			err = EFAULT;
364			break;
365		}
366
367		if ((err = xen_bind_interdomain(bind.remote_domain,
368		    bind.remote_port, rvalp)) != 0)
369			break;
370
371		ec_bind_vcpu(*rvalp, 0);
372		evtchn_bind_to_user(ep, *rvalp);
373		break;
374	}
375
376	case IOCTL_EVTCHN_BIND_UNBOUND_PORT: {
377		struct ioctl_evtchn_bind_unbound_port bind;
378
379		if (copyin((void *)data, &bind, sizeof (bind))) {
380			err = EFAULT;
381			break;
382		}
383
384		if ((err = xen_alloc_unbound_evtchn(bind.remote_domain,
385		    rvalp)) != 0)
386			break;
387
388		evtchn_bind_to_user(ep, *rvalp);
389		break;
390	}
391
392	case IOCTL_EVTCHN_UNBIND: {
393		struct ioctl_evtchn_unbind unbind;
394
395		if (copyin((void *)data, &unbind, sizeof (unbind))) {
396			err = EFAULT;
397			break;
398		}
399
400		if (unbind.port >= NR_EVENT_CHANNELS) {
401			err = EFAULT;
402			break;
403		}
404
405		mutex_enter(&port_user_lock);
406
407		if (port_user[unbind.port] != ep) {
408			mutex_exit(&port_user_lock);
409			err = ENOTCONN;
410			break;
411		}
412
413		evtchndrv_close_evtchn(unbind.port);
414		mutex_exit(&port_user_lock);
415		break;
416	}
417
418	case IOCTL_EVTCHN_NOTIFY: {
419		struct ioctl_evtchn_notify notify;
420
421		if (copyin((void *)data, &notify, sizeof (notify))) {
422			err = EFAULT;
423			break;
424		}
425
426		if (notify.port >= NR_EVENT_CHANNELS) {
427			err = EINVAL;
428		} else if (port_user[notify.port] != ep) {
429			err = ENOTCONN;
430		} else {
431			ec_notify_via_evtchn(notify.port);
432		}
433		break;
434	}
435
436	default:
437		err = ENOSYS;
438	}
439
440	return (err);
441}
442
443static int
444evtchndrv_poll(dev_t dev, short ev, int anyyet, short *revp, pollhead_t **phpp)
445{
446	struct evtsoftdata *ep;
447	minor_t minor = getminor(dev);
448	short mask = 0;
449
450	ep = EVTCHNDRV_INST2SOFTS(EVTCHNDRV_MINOR2INST(minor));
451	*phpp = (struct pollhead *)NULL;
452
453	if (ev & POLLOUT)
454		mask |= POLLOUT;
455	if (ep->ring_overflow)
456		mask |= POLLERR;
457	if (ev & (POLLIN | POLLRDNORM)) {
458		mutex_enter(&ep->evtchn_lock);
459		if (ep->ring_cons != ep->ring_prod)
460			mask |= (POLLIN | POLLRDNORM) & ev;
461		else
462			if (mask == 0 && !anyyet)
463				*phpp = &ep->evtchn_pollhead;
464		mutex_exit(&ep->evtchn_lock);
465	}
466	*revp = mask;
467	return (0);
468}
469
470
471/* ARGSUSED */
472static int
473evtchndrv_open(dev_t *devp, int flag, int otyp, cred_t *credp)
474{
475	struct evtsoftdata *ep;
476	minor_t minor = getminor(*devp);
477
478	if (otyp == OTYP_BLK)
479		return (ENXIO);
480
481	/*
482	 * only allow open on minor = 0 - the clone device
483	 */
484	if (minor != 0)
485		return (ENXIO);
486
487	/*
488	 * find a free slot and grab it
489	 */
490	mutex_enter(&evtchndrv_clone_tab_mutex);
491	for (minor = 1; minor < evtchndrv_nclones; minor++) {
492		if (evtchndrv_clone_tab[minor] == 0) {
493			evtchndrv_clone_tab[minor] = 1;
494			break;
495		}
496	}
497	mutex_exit(&evtchndrv_clone_tab_mutex);
498	if (minor == evtchndrv_nclones)
499		return (EAGAIN);
500
501	/* Allocate softstate structure */
502	if (ddi_soft_state_zalloc(evtchndrv_statep,
503	    EVTCHNDRV_MINOR2INST(minor)) != DDI_SUCCESS) {
504		mutex_enter(&evtchndrv_clone_tab_mutex);
505		evtchndrv_clone_tab[minor] = 0;
506		mutex_exit(&evtchndrv_clone_tab_mutex);
507		return (EAGAIN);
508	}
509	ep = EVTCHNDRV_INST2SOFTS(EVTCHNDRV_MINOR2INST(minor));
510
511	/* ... and init it */
512	ep->dip = evtchndrv_dip;
513
514	cv_init(&ep->evtchn_wait, NULL, CV_DEFAULT, NULL);
515	mutex_init(&ep->evtchn_lock, NULL, MUTEX_DEFAULT, NULL);
516
517	ep->ring = kmem_alloc(PAGESIZE, KM_SLEEP);
518
519	/* clone driver */
520	*devp = makedevice(getmajor(*devp), minor);
521
522	return (0);
523}
524
525/* ARGSUSED */
526static int
527evtchndrv_close(dev_t dev, int flag, int otyp, struct cred *credp)
528{
529	struct evtsoftdata *ep;
530	minor_t minor = getminor(dev);
531	int i;
532
533	ep = EVTCHNDRV_INST2SOFTS(EVTCHNDRV_MINOR2INST(minor));
534	if (ep == NULL)
535		return (ENXIO);
536
537	mutex_enter(&port_user_lock);
538
539
540	for (i = 0; i < NR_EVENT_CHANNELS; i++) {
541		if (port_user[i] != ep)
542			continue;
543
544		evtchndrv_close_evtchn(i);
545	}
546
547	mutex_exit(&port_user_lock);
548
549	kmem_free(ep->ring, PAGESIZE);
550	ddi_soft_state_free(evtchndrv_statep, EVTCHNDRV_MINOR2INST(minor));
551
552	/*
553	 * free clone tab slot
554	 */
555	mutex_enter(&evtchndrv_clone_tab_mutex);
556	evtchndrv_clone_tab[minor] = 0;
557	mutex_exit(&evtchndrv_clone_tab_mutex);
558
559	return (0);
560}
561
562/* ARGSUSED */
563static int
564evtchndrv_info(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
565{
566	dev_t	dev = (dev_t)arg;
567	minor_t	minor = getminor(dev);
568	int	retval;
569
570	switch (cmd) {
571	case DDI_INFO_DEVT2DEVINFO:
572		if (minor != 0 || evtchndrv_dip == NULL) {
573			*result = (void *)NULL;
574			retval = DDI_FAILURE;
575		} else {
576			*result = (void *)evtchndrv_dip;
577			retval = DDI_SUCCESS;
578		}
579		break;
580	case DDI_INFO_DEVT2INSTANCE:
581		*result = (void *)0;
582		retval = DDI_SUCCESS;
583		break;
584	default:
585		retval = DDI_FAILURE;
586	}
587	return (retval);
588}
589
590
591static int
592evtchndrv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
593{
594	int	error;
595	int	unit = ddi_get_instance(dip);
596
597
598	switch (cmd) {
599	case DDI_ATTACH:
600		break;
601	case DDI_RESUME:
602		return (DDI_SUCCESS);
603	default:
604		cmn_err(CE_WARN, "evtchn_attach: unknown cmd 0x%x\n", cmd);
605		return (DDI_FAILURE);
606	}
607
608	/* DDI_ATTACH */
609
610	/*
611	 * only one instance - but we clone using the open routine
612	 */
613	if (ddi_get_instance(dip) > 0)
614		return (DDI_FAILURE);
615
616	mutex_init(&evtchndrv_clone_tab_mutex, NULL, MUTEX_DRIVER,
617	    NULL);
618
619	error = ddi_create_minor_node(dip, "evtchn", S_IFCHR, unit,
620	    DDI_PSEUDO, NULL);
621	if (error != DDI_SUCCESS)
622		goto fail;
623
624	/*
625	 * save dip for getinfo
626	 */
627	evtchndrv_dip = dip;
628	ddi_report_dev(dip);
629
630	mutex_init(&port_user_lock, NULL, MUTEX_DRIVER, NULL);
631	(void) memset(port_user, 0, sizeof (port_user));
632
633	ec_dev_irq = ec_dev_alloc_irq();
634	(void) add_avintr(NULL, IPL_EVTCHN, (avfunc)evtchn_device_upcall,
635	    "evtchn_driver", ec_dev_irq, NULL, NULL, NULL, dip);
636
637	return (DDI_SUCCESS);
638
639fail:
640	(void) evtchndrv_detach(dip, DDI_DETACH);
641	return (error);
642}
643
644/*ARGSUSED*/
645static int
646evtchndrv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
647{
648	/*
649	 * Don't allow detach for now.
650	 */
651	return (DDI_FAILURE);
652}
653
654/* Solaris driver framework */
655
656static 	struct cb_ops evtchndrv_cb_ops = {
657	evtchndrv_open,		/* cb_open */
658	evtchndrv_close,	/* cb_close */
659	nodev,			/* cb_strategy */
660	nodev,			/* cb_print */
661	nodev,			/* cb_dump */
662	evtchndrv_read,		/* cb_read */
663	evtchndrv_write,	/* cb_write */
664	evtchndrv_ioctl,	/* cb_ioctl */
665	nodev,			/* cb_devmap */
666	nodev,			/* cb_mmap */
667	nodev,			/* cb_segmap */
668	evtchndrv_poll,		/* cb_chpoll */
669	ddi_prop_op,		/* cb_prop_op */
670	0,			/* cb_stream */
671	D_NEW | D_MP | D_64BIT	/* cb_flag */
672};
673
674static struct dev_ops evtchndrv_dev_ops = {
675	DEVO_REV,		/* devo_rev */
676	0,			/* devo_refcnt */
677	evtchndrv_info,		/* devo_getinfo */
678	nulldev,		/* devo_identify */
679	nulldev,		/* devo_probe */
680	evtchndrv_attach,	/* devo_attach */
681	evtchndrv_detach,	/* devo_detach */
682	nodev,			/* devo_reset */
683	&evtchndrv_cb_ops,	/* devo_cb_ops */
684	NULL,			/* devo_bus_ops */
685	NULL,			/* power */
686	ddi_quiesce_not_needed,		/* devo_quiesce */
687};
688
689static struct modldrv modldrv = {
690	&mod_driverops,		/* Type of module.  This one is a driver */
691	"Evtchn driver",	/* Name of the module. */
692	&evtchndrv_dev_ops	/* driver ops */
693};
694
695static struct modlinkage modlinkage = {
696	MODREV_1,
697	&modldrv,
698	NULL
699};
700
701int
702_init(void)
703{
704	int err;
705
706	err = ddi_soft_state_init(&evtchndrv_statep,
707	    sizeof (struct evtsoftdata), 1);
708	if (err)
709		return (err);
710
711	err = mod_install(&modlinkage);
712	if (err)
713		ddi_soft_state_fini(&evtchndrv_statep);
714	else
715		evtchndrv_clone_tab = kmem_zalloc(
716		    sizeof (int) * evtchndrv_nclones, KM_SLEEP);
717	return (err);
718}
719
720int
721_fini(void)
722{
723	int e;
724
725	e = mod_remove(&modlinkage);
726	if (e)
727		return (e);
728
729	ddi_soft_state_fini(&evtchndrv_statep);
730
731	return (0);
732}
733
734int
735_info(struct modinfo *modinfop)
736{
737	return (mod_info(&modlinkage, modinfop));
738}
739