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