xref: /dragonfly/sys/dev/misc/nmdm/nmdm.c (revision 2b3f93ea)
1 /*
2  * (MPSAFE)
3  *
4  * Copyright (c) 1982, 1986, 1989, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  * $FreeBSD: src/sys/dev/nmdm/nmdm.c,v 1.5.2.1 2001/08/11 00:54:14 mp Exp $
32  */
33 
34 /*
35  * Pseudo-nulmodem Driver
36  */
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/uio.h>
40 #include <sys/proc.h>
41 #include <sys/caps.h>
42 #include <sys/tty.h>
43 #include <sys/ttydefaults.h>	/* for TTYDEF_* */
44 #include <sys/conf.h>
45 #include <sys/fcntl.h>
46 #include <sys/kernel.h>
47 #include <sys/vnode.h>
48 #include <sys/signalvar.h>
49 #include <sys/malloc.h>
50 
51 MALLOC_DEFINE(M_NLMDM, "nullmodem", "nullmodem data structures");
52 
53 static void nmdmstart (struct tty *tp);
54 static void nmdmstop (struct tty *tp, int rw);
55 static void wakeup_other (struct tty *tp, int flag);
56 static void nmdminit (int n);
57 
58 static	d_open_t	nmdmopen;
59 static	d_close_t	nmdmclose;
60 static	d_read_t	nmdmread;
61 static	d_write_t	nmdmwrite;
62 static	d_ioctl_t	nmdmioctl;
63 
64 #define	CDEV_MAJOR	18
65 static struct dev_ops nmdm_ops = {
66 	{ "pts", 0, D_TTY },
67 	.d_open =	nmdmopen,
68 	.d_close =	nmdmclose,
69 	.d_read =	nmdmread,
70 	.d_write =	nmdmwrite,
71 	.d_ioctl =	nmdmioctl,
72 	.d_kqfilter = 	ttykqfilter,
73 	.d_revoke =	ttyrevoke
74 };
75 
76 #define BUFSIZ 100		/* Chunk size iomoved to/from user */
77 
78 struct softpart {
79 	struct tty nm_tty;
80 	cdev_t	dev;
81 	int	modemsignals;	/* bits defined in sys/ttycom.h */
82 	int	gotbreak;
83 };
84 
85 struct	nm_softc {
86 	int	pt_flags;
87 	struct softpart part1, part2;
88 	struct	prison *pt_prison;
89 };
90 
91 #define	PF_STOPPED	0x10		/* user told stopped */
92 
93 static void
94 nmdm_crossover(struct nm_softc *pti,
95 		struct softpart *ourpart,
96 		struct softpart *otherpart);
97 
98 #define GETPARTS(tp, ourpart, otherpart) \
99 do {	\
100 	struct nm_softc *pti = tp->t_dev->si_drv1; \
101 	if (tp == &pti->part1.nm_tty) { \
102 		ourpart = &pti->part1; \
103 		otherpart = &pti->part2; \
104 	} else { \
105 		ourpart = &pti->part2; \
106 		otherpart = &pti->part1; \
107 	}  \
108 } while (0)
109 
110 /*
111  * This function creates and initializes a pair of ttys.
112  */
113 static void
nmdminit(int n)114 nmdminit(int n)
115 {
116 	cdev_t dev1, dev2;
117 	struct nm_softc *pt;
118 
119 	/*
120 	 * Simplified unit number, use low 8 bits of minor number
121 	 * (remember, the minor number mask is 0xffff00ff).
122 	 */
123 	if (n & ~0x7f)
124 		return;
125 
126 	pt = kmalloc(sizeof(*pt), M_NLMDM, M_WAITOK | M_ZERO);
127 	pt->part1.dev = dev1 = make_dev(&nmdm_ops, n << 1,
128 					0, 0, 0666, "nmdm%dA", n);
129 	pt->part2.dev = dev2 = make_dev(&nmdm_ops, (n << 1) + 1,
130 					0, 0, 0666, "nmdm%dB", n);
131 
132 	dev1->si_drv1 = dev2->si_drv1 = pt;
133 	dev1->si_tty = &pt->part1.nm_tty;
134 	dev2->si_tty = &pt->part2.nm_tty;
135 	ttyinit(&pt->part1.nm_tty);
136 	ttyinit(&pt->part2.nm_tty);
137 	ttyregister(&pt->part1.nm_tty);
138 	ttyregister(&pt->part2.nm_tty);
139 	pt->part1.nm_tty.t_oproc = nmdmstart;
140 	pt->part2.nm_tty.t_oproc = nmdmstart;
141 	pt->part1.nm_tty.t_stop = nmdmstop;
142 	pt->part2.nm_tty.t_dev = dev1;
143 	pt->part1.nm_tty.t_dev = dev2;
144 	pt->part2.nm_tty.t_stop = nmdmstop;
145 }
146 
147 /*ARGSUSED*/
148 static	int
nmdmopen(struct dev_open_args * ap)149 nmdmopen(struct dev_open_args *ap)
150 {
151 	cdev_t dev = ap->a_head.a_dev;
152 	struct tty *tp, *tp2;
153 	int error;
154 	int minr;
155 #if 0
156 	cdev_t nextdev;
157 #endif
158 	struct nm_softc *pti;
159 	int is_b;
160 	int	pair;
161 	struct	softpart *ourpart, *otherpart;
162 
163 	minr = lminor(dev);
164 	pair = minr >> 1;
165 	is_b = minr & 1;
166 
167 #if 0
168 	/*
169 	 * XXX: Gross hack for DEVFS:
170 	 * If we openned this device, ensure we have the
171 	 * next one too, so people can open it.
172 	 */
173 	if (pair < 127) {
174 		nextdev = makedev(major(dev), (pair+pair) + 1);
175 		if (!nextdev->si_drv1) {
176 			nmdminit(pair + 1);
177 		}
178 	}
179 #endif
180 	if (!dev->si_drv1)
181 		nmdminit(pair);
182 
183 	if (!dev->si_drv1)
184 		return(ENXIO);
185 
186 	pti = dev->si_drv1;
187 	if (is_b)
188 		tp = &pti->part2.nm_tty;
189 	else
190 		tp = &pti->part1.nm_tty;
191 	lwkt_gettoken(&tp->t_token);
192 	GETPARTS(tp, ourpart, otherpart);
193 	tp2 = &otherpart->nm_tty;
194 	lwkt_gettoken(&tp2->t_token);
195 	ourpart->modemsignals |= TIOCM_LE;
196 
197 	if ((tp->t_state & TS_ISOPEN) == 0) {
198 		ttychars(tp);		/* Set up default chars */
199 		tp->t_iflag = TTYDEF_IFLAG;
200 		tp->t_oflag = TTYDEF_OFLAG;
201 		tp->t_lflag = TTYDEF_LFLAG;
202 		tp->t_cflag = TTYDEF_CFLAG;
203 		tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
204 	} else if (tp->t_state & TS_XCLUDE &&
205 		   caps_priv_check(ap->a_cred, SYSCAP_RESTRICTEDROOT))
206 	{
207 		lwkt_reltoken(&tp2->t_token);
208 		lwkt_reltoken(&tp->t_token);
209 
210 		return (EBUSY);
211 	} else if (pti->pt_prison != ap->a_cred->cr_prison) {
212 		lwkt_reltoken(&tp2->t_token);
213 		lwkt_reltoken(&tp->t_token);
214 
215 		return (EBUSY);
216 	}
217 
218 	/*
219 	 * If the other side is open we have carrier
220 	 */
221 	if (tp2->t_state & TS_ISOPEN) {
222 		(void)(*linesw[tp->t_line].l_modem)(tp, 1);
223 	}
224 
225 	/*
226 	 * And the other side gets carrier as we are now open.
227 	 */
228 	(void)(*linesw[tp2->t_line].l_modem)(tp2, 1);
229 
230 	/* External processing makes no sense here */
231 	tp->t_lflag &= ~EXTPROC;
232 
233 	/*
234 	 * Wait here if we don't have carrier.
235 	 */
236 #if 0
237 	while ((tp->t_state & TS_CARR_ON) == 0) {
238 		if (flag & FNONBLOCK)
239 			break;
240 		error = ttysleep(tp, TSA_CARR_ON(tp), PCATCH, "nmdopn", 0);
241 		if (error) {
242 			lwkt_reltoken(&tp2->t_token);
243 			lwkt_reltoken(&tp->t_token);
244 
245 			return (error);
246 		}
247 	}
248 #endif
249 
250 	/*
251 	 * Give the line disciplin a chance to set this end up.
252 	 */
253 	error = (*linesw[tp->t_line].l_open)(dev, tp);
254 
255 	/*
256 	 * Wake up the other side.
257 	 * Theoretically not needed.
258 	 */
259 	ourpart->modemsignals |= TIOCM_DTR;
260 	nmdm_crossover(pti, ourpart, otherpart);
261 	if (error == 0)
262 		wakeup_other(tp, FREAD|FWRITE); /* XXX */
263 	lwkt_reltoken(&tp2->t_token);
264 	lwkt_reltoken(&tp->t_token);
265 
266 	return (error);
267 }
268 
269 static int
nmdmclose(struct dev_close_args * ap)270 nmdmclose(struct dev_close_args *ap)
271 {
272 	cdev_t dev = ap->a_head.a_dev;
273 	struct tty *tp, *tp2;
274 	int err;
275 	struct softpart *ourpart, *otherpart;
276 
277 	/*
278 	 * let the other end know that the game is up
279 	 */
280 	tp = dev->si_tty;
281 	lwkt_gettoken(&tp->t_token);
282 	GETPARTS(tp, ourpart, otherpart);
283 	tp2 = &otherpart->nm_tty;
284 	lwkt_gettoken(&tp2->t_token);
285 	(void)(*linesw[tp2->t_line].l_modem)(tp2, 0);
286 
287 	/*
288 	 * XXX MDMBUF makes no sense for nmdms but would inhibit the above
289 	 * l_modem().  CLOCAL makes sense but isn't supported.   Special
290 	 * l_modem()s that ignore carrier drop make no sense for nmdms but
291 	 * may be in use because other parts of the line discipline make
292 	 * sense for nmdms.  Recover by doing everything that a normal
293 	 * ttymodem() would have done except for sending a SIGHUP.
294 	 */
295 	if (tp2->t_state & TS_ISOPEN) {
296 		tp2->t_state &= ~(TS_CARR_ON | TS_CONNECTED);
297 		tp2->t_state |= TS_ZOMBIE;
298 		ttyflush(tp2, FREAD | FWRITE);
299 	}
300 
301 	err = (*linesw[tp->t_line].l_close)(tp, ap->a_fflag);
302 	ourpart->modemsignals &= ~TIOCM_DTR;
303 	nmdm_crossover(dev->si_drv1, ourpart, otherpart);
304 	nmdmstop(tp, FREAD|FWRITE);
305 	ttyclose(tp);
306 	lwkt_reltoken(&tp2->t_token);
307 	lwkt_reltoken(&tp->t_token);
308 
309 	return (err);
310 }
311 
312 static int
nmdmread(struct dev_read_args * ap)313 nmdmread(struct dev_read_args *ap)
314 {
315 	cdev_t dev = ap->a_head.a_dev;
316 	int error = 0;
317 	struct tty *tp;
318 #if 0
319 	struct tty *tp2;
320 	struct softpart *ourpart, *otherpart;
321 #endif
322 
323 	tp = dev->si_tty;
324 	lwkt_gettoken(&tp->t_token);
325 #if 0
326 	GETPARTS(tp, ourpart, otherpart);
327 	tp2 = &otherpart->nm_tty;
328 	lwkt_gettoken(&tp2->t_token);
329 
330 	if (tp2->t_state & TS_ISOPEN) {
331 		error = (*linesw[tp->t_line].l_read)(tp, ap->a_uio, flag);
332 		wakeup_other(tp, FWRITE);
333 	} else {
334 		if (flag & IO_NDELAY) {
335 			lwkt_reltoken(&tp2->t_token);
336 			lwkt_reltoken(&tp->t_token);
337 			return (EWOULDBLOCK);
338 		}
339 		error = tsleep(TSA_PTC_READ(tp), PCATCH, "nmdout", 0);
340 		}
341 	}
342 	lwkt_reltoken(&tp2->t_token);
343 #else
344 	if ((error = (*linesw[tp->t_line].l_read)(tp, ap->a_uio, ap->a_ioflag)) == 0)
345 		wakeup_other(tp, FWRITE);
346 #endif
347 	lwkt_reltoken(&tp->t_token);
348 
349 	return (error);
350 }
351 
352 /*
353  * Write to pseudo-tty.
354  * Wakeups of controlling tty will happen
355  * indirectly, when tty driver calls nmdmstart.
356  */
357 static	int
nmdmwrite(struct dev_write_args * ap)358 nmdmwrite(struct dev_write_args *ap)
359 {
360 	cdev_t dev = ap->a_head.a_dev;
361 	struct uio *uio = ap->a_uio;
362 	u_char *cp = NULL;
363 	size_t cc = 0;
364 	u_char locbuf[BUFSIZ];
365 	int cnt = 0;
366 	int error = 0;
367 	struct tty *tp1, *tp;
368 	struct softpart *ourpart, *otherpart;
369 
370 	tp1 = dev->si_tty;
371 	lwkt_gettoken(&tp1->t_token);
372 	/*
373 	 * Get the other tty struct.
374 	 * basically we are writing into the INPUT side of the other device.
375 	 */
376 	GETPARTS(tp1, ourpart, otherpart);
377 	tp = &otherpart->nm_tty;
378 	lwkt_gettoken(&tp->t_token);
379 
380 again:
381 	if ((tp->t_state & TS_ISOPEN) == 0) {
382 		lwkt_reltoken(&tp->t_token);
383 		lwkt_reltoken(&tp1->t_token);
384 		return (EIO);
385 	}
386 	while (uio->uio_resid > 0 || cc > 0) {
387 		/*
388 		 * Fill up the buffer if it's empty
389 		 */
390 		if (cc == 0) {
391 			cc = szmin(uio->uio_resid, BUFSIZ);
392 			cp = locbuf;
393 			error = uiomove((caddr_t)cp, cc, uio);
394 			if (error) {
395 				lwkt_reltoken(&tp->t_token);
396 				lwkt_reltoken(&tp1->t_token);
397 				return (error);
398 			}
399 			/* check again for safety */
400 			if ((tp->t_state & TS_ISOPEN) == 0) {
401 				/* adjust for data copied in but not written */
402 				uio->uio_resid += cc;
403 				lwkt_reltoken(&tp->t_token);
404 				lwkt_reltoken(&tp1->t_token);
405 				return (EIO);
406 			}
407 		}
408 		while (cc > 0) {
409 			if (((tp->t_rawq.c_cc + tp->t_canq.c_cc) >= (TTYHOG-2))
410 			&& ((tp->t_canq.c_cc > 0) || !(tp->t_iflag&ICANON))) {
411 				/*
412 	 			 * Come here to wait for space in outq,
413 				 * or space in rawq, or an empty canq.
414 	 			 */
415 				wakeup(TSA_HUP_OR_INPUT(tp));
416 				if ((tp->t_state & TS_CONNECTED) == 0) {
417 					/*
418 					 * Data piled up because not connected.
419 					 * Adjust for data copied in but
420 					 * not written.
421 					 */
422 					uio->uio_resid += cc;
423 					lwkt_reltoken(&tp->t_token);
424 					lwkt_reltoken(&tp1->t_token);
425 					return (EIO);
426 				}
427 				if (ap->a_ioflag & IO_NDELAY) {
428 					/*
429 				         * Don't wait if asked not to.
430 					 * Adjust for data copied in but
431 					 * not written.
432 					 */
433 					uio->uio_resid += cc;
434 					if (cnt == 0) {
435 						lwkt_reltoken(&tp->t_token);
436 						lwkt_reltoken(&tp1->t_token);
437 						return (EWOULDBLOCK);
438 					}
439 					lwkt_reltoken(&tp->t_token);
440 					lwkt_reltoken(&tp1->t_token);
441 					return (0);
442 				}
443 				error = tsleep(TSA_PTC_WRITE(tp),
444 						PCATCH, "nmdout", 0);
445 				if (error) {
446 					/*
447 					 * Tsleep returned (signal?).
448 					 * Go find out what the user wants.
449 					 * adjust for data copied in but
450 					 * not written
451 					 */
452 					uio->uio_resid += cc;
453 					lwkt_reltoken(&tp->t_token);
454 					lwkt_reltoken(&tp1->t_token);
455 					return (error);
456 				}
457 				goto again;
458 			}
459 			(*linesw[tp->t_line].l_rint)(*cp++, tp);
460 			cnt++;
461 			cc--;
462 		}
463 		cc = 0;
464 	}
465 	lwkt_reltoken(&tp->t_token);
466 	lwkt_reltoken(&tp1->t_token);
467 
468 	return (0);
469 }
470 
471 /*
472  * Start output on pseudo-tty.
473  * Wake up process selecting or sleeping for input from controlling tty.
474  */
475 static void
nmdmstart(struct tty * tp)476 nmdmstart(struct tty *tp)
477 {
478 	struct nm_softc *pti = tp->t_dev->si_drv1;
479 
480 	lwkt_gettoken(&tp->t_token);
481 	if (tp->t_state & TS_TTSTOP) {
482 		lwkt_reltoken(&tp->t_token);
483 		return;
484 	}
485 	pti->pt_flags &= ~PF_STOPPED;
486 	wakeup_other(tp, FREAD);
487 	lwkt_reltoken(&tp->t_token);
488 }
489 
490 /*
491  * Wakes up the OTHER tty.  Caller must hold at least tp->t_token.
492  */
493 static void
wakeup_other(struct tty * tp,int flag)494 wakeup_other(struct tty *tp, int flag)
495 {
496 	struct softpart *ourpart, *otherpart;
497 
498 	GETPARTS(tp, ourpart, otherpart);
499 	lwkt_gettoken(&otherpart->nm_tty.t_token);
500 	if (flag & FREAD) {
501 		wakeup(TSA_PTC_READ((&otherpart->nm_tty)));
502 		KNOTE(&otherpart->nm_tty.t_rkq.ki_note, 0);
503 	}
504 	if (flag & FWRITE) {
505 		wakeup(TSA_PTC_WRITE((&otherpart->nm_tty)));
506 		KNOTE(&otherpart->nm_tty.t_wkq.ki_note, 0);
507 	}
508 	lwkt_reltoken(&otherpart->nm_tty.t_token);
509 }
510 
511 static	void
nmdmstop(struct tty * tp,int flush)512 nmdmstop(struct tty *tp, int flush)
513 {
514 	struct nm_softc *pti = tp->t_dev->si_drv1;
515 	int flag;
516 
517 	lwkt_gettoken(&tp->t_token);
518 	/* note: FLUSHREAD and FLUSHWRITE already ok */
519 	if (flush == 0) {
520 		flush = TIOCPKT_STOP;
521 		pti->pt_flags |= PF_STOPPED;
522 	} else
523 		pti->pt_flags &= ~PF_STOPPED;
524 	/* change of perspective */
525 	flag = 0;
526 	if (flush & FREAD)
527 		flag |= FWRITE;
528 	if (flush & FWRITE)
529 		flag |= FREAD;
530 	wakeup_other(tp, flag);
531 	lwkt_reltoken(&tp->t_token);
532 }
533 
534 /*ARGSUSED*/
535 static	int
nmdmioctl(struct dev_ioctl_args * ap)536 nmdmioctl(struct dev_ioctl_args *ap)
537 {
538 	cdev_t dev = ap->a_head.a_dev;
539 	struct tty *tp = dev->si_tty;
540 	struct tty *tp2;
541 	struct nm_softc *pti = dev->si_drv1;
542 	int error;
543 	struct softpart *ourpart, *otherpart;
544 
545 	lwkt_gettoken(&tp->t_token);
546 	GETPARTS(tp, ourpart, otherpart);
547 	tp2 = &otherpart->nm_tty;
548 	lwkt_gettoken(&tp2->t_token);
549 
550 	error = (*linesw[tp->t_line].l_ioctl)(tp, ap->a_cmd, ap->a_data,
551 					      ap->a_fflag, ap->a_cred);
552 	if (error == ENOIOCTL)
553 		 error = ttioctl(tp, ap->a_cmd, ap->a_data, ap->a_fflag);
554 	if (error == ENOIOCTL) {
555 		switch (ap->a_cmd) {
556 		case TIOCSBRK:
557 			otherpart->gotbreak = 1;
558 			break;
559 		case TIOCCBRK:
560 			break;
561 		case TIOCSDTR:
562 			ourpart->modemsignals |= TIOCM_DTR;
563 			break;
564 		case TIOCCDTR:
565 			ourpart->modemsignals &= TIOCM_DTR;
566 			break;
567 		case TIOCMSET:
568 			ourpart->modemsignals = *(int *)ap->a_data;
569 			otherpart->modemsignals = *(int *)ap->a_data;
570 			break;
571 		case TIOCMBIS:
572 			ourpart->modemsignals |= *(int *)ap->a_data;
573 			break;
574 		case TIOCMBIC:
575 			ourpart->modemsignals &= ~(*(int *)ap->a_data);
576 			otherpart->modemsignals &= ~(*(int *)ap->a_data);
577 			break;
578 		case TIOCMGET:
579 			*(int *)ap->a_data = ourpart->modemsignals;
580 			break;
581 		case TIOCMSDTRWAIT:
582 			break;
583 		case TIOCMGDTRWAIT:
584 			*(int *)ap->a_data = 0;
585 			break;
586 		case TIOCTIMESTAMP:
587 		case TIOCDCDTIMESTAMP:
588 		default:
589 			lwkt_reltoken(&tp2->t_token);
590 			lwkt_reltoken(&tp->t_token);
591 			error = ENOTTY;
592 			return (error);
593 		}
594 		error = 0;
595 		nmdm_crossover(pti, ourpart, otherpart);
596 	}
597 	lwkt_reltoken(&tp2->t_token);
598 	lwkt_reltoken(&tp->t_token);
599 
600 	return (error);
601 }
602 
603 /*
604  * Caller must hold both tty tokens
605  */
606 static void
nmdm_crossover(struct nm_softc * pti,struct softpart * ourpart,struct softpart * otherpart)607 nmdm_crossover(struct nm_softc *pti,
608 		struct softpart *ourpart,
609 		struct softpart *otherpart)
610 {
611 	otherpart->modemsignals &= ~(TIOCM_CTS|TIOCM_CAR);
612 	if (ourpart->modemsignals & TIOCM_RTS)
613 		otherpart->modemsignals |= TIOCM_CTS;
614 	if (ourpart->modemsignals & TIOCM_DTR)
615 		otherpart->modemsignals |= TIOCM_CAR;
616 }
617 
618 
619 
620 static void nmdm_drvinit (void *unused);
621 
622 static void
nmdm_drvinit(void * unused)623 nmdm_drvinit(void *unused)
624 {
625 	nmdminit(0);
626 }
627 
628 SYSINIT(nmdmdev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE + CDEV_MAJOR, nmdm_drvinit,
629     NULL);
630