nmdm.c revision 111899
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 111899 2003-03-05 08:16:29Z das $
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
59MALLOC_DEFINE(M_NLMDM, "nullmodem", "nullmodem data structures");
60
61static void 	nmdmstart(struct tty *tp);
62static void 	nmdmstop(struct tty *tp, int rw);
63static void 	wakeup_other(struct tty *tp, int flag);
64static void 	nmdminit(int);
65static int 	nmdmshutdown(void);
66
67static d_open_t		nmdmopen;
68static d_close_t	nmdmclose;
69static d_read_t		nmdmread;
70static d_write_t	nmdmwrite;
71static d_ioctl_t	nmdmioctl;
72
73#define	CDEV_MAJOR	18
74static 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
90struct softpart {
91	struct tty	nm_tty;
92	dev_t	dev;
93	int	modemsignals;	/* bits defined in sys/ttycom.h */
94	int	gotbreak;
95};
96
97struct	nm_softc {
98	int	pt_flags;
99	struct softpart part1, part2;
100	struct	prison *pt_prison;
101};
102
103static void
104nmdm_crossover(struct nm_softc *pti,
105		struct softpart *ourpart,
106		struct softpart *otherpart);
107
108#define GETPARTS(tp, ourpart, otherpart) \
109do {	\
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 */
123static void
124nmdminit(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 */
157static	int
158nmdmopen(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 */
264static	int
265nmdmclose(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 */
304static	int
305nmdmread(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 */
339static	int
340nmdmwrite(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
358again:
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 */
433static void
434nmdmstart(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;*/
445static void
446wakeup_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 */
464static	void
465nmdmstop(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 */
488static	int
489nmdmioctl(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
551static void
552nmdm_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 */
565static int
566nmdm_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 */
592static int
593nmdmshutdown(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
616DEV_MODULE(nmdm, nmdm_modevent, NULL);
617