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