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