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