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