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