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