nmdm.c revision 129939
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 129939 2004-06-01 11:57:15Z 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	(void) ttyclose(tp);
317	return (err);
318}
319
320/*
321 * handle read(2) request from userland
322 */
323static	int
324nmdmread(dev_t dev, struct uio *uio, int flag)
325{
326	int error = 0;
327	struct tty *tp, *tp2;
328	struct softpart *ourpart, *otherpart;
329
330	tp = dev->si_tty;
331	GETPARTS(tp, ourpart, otherpart);
332	tp2 = &otherpart->nm_tty;
333
334#if 0
335	if (tp2->t_state & TS_ISOPEN) {
336		error = (*linesw[tp->t_line].l_read)(tp, uio, flag);
337		wakeup_other(tp, FWRITE);
338	} else {
339		if (flag & IO_NDELAY) {
340			return (EWOULDBLOCK);
341		}
342		error = tsleep(TSA_PTC_READ(tp),
343				TTIPRI | PCATCH, "nmdout", 0);
344		}
345	}
346#else
347	if ((error = (*linesw[tp->t_line].l_read)(tp, uio, flag)) == 0)
348		wakeup_other(tp, FWRITE);
349#endif
350	return (error);
351}
352
353/*
354 * Write to pseudo-tty.
355 * Wakeups of controlling tty will happen
356 * indirectly, when tty driver calls nmdmstart.
357 */
358static	int
359nmdmwrite(dev_t dev, struct uio *uio, int flag)
360{
361	register u_char *cp = 0;
362	register int cc = 0;
363	u_char locbuf[BUFSIZ];
364	int cnt = 0;
365	int error = 0;
366	struct tty *tp1, *tp;
367	struct softpart *ourpart, *otherpart;
368
369	tp1 = dev->si_tty;
370	/*
371	 * Get the other tty struct.
372	 * basically we are writing into the INPUT side of the other device.
373	 */
374	GETPARTS(tp1, ourpart, otherpart);
375	tp = &otherpart->nm_tty;
376
377again:
378	if ((tp->t_state & TS_ISOPEN) == 0)
379		return (EIO);
380	while (uio->uio_resid > 0 || cc > 0) {
381		/*
382		 * Fill up the buffer if it's empty
383		 */
384		if (cc == 0) {
385			cc = min(uio->uio_resid, BUFSIZ);
386			cp = locbuf;
387			error = uiomove((caddr_t)cp, cc, uio);
388			if (error)
389				return (error);
390			/* check again for safety */
391			if ((tp->t_state & TS_ISOPEN) == 0) {
392				/* adjust for data copied in but not written */
393				uio->uio_resid += cc;
394				return (EIO);
395			}
396		}
397		while (cc > 0) {
398			if (((tp->t_rawq.c_cc + tp->t_canq.c_cc) >= (TTYHOG-2))
399			&& ((tp->t_canq.c_cc > 0) || !(tp->t_iflag&ICANON))) {
400				/*
401	 			 * Come here to wait for space in outq,
402				 * or space in rawq, or an empty canq.
403	 			 */
404				wakeup(TSA_HUP_OR_INPUT(tp));
405				if ((tp->t_state & TS_CONNECTED) == 0) {
406					/*
407					 * Data piled up because not connected.
408					 * Adjust for data copied in but
409					 * not written.
410					 */
411					uio->uio_resid += cc;
412					return (EIO);
413				}
414				if (flag & IO_NDELAY) {
415					/*
416				         * Don't wait if asked not to.
417					 * Adjust for data copied in but
418					 * not written.
419					 */
420					uio->uio_resid += cc;
421					if (cnt == 0)
422						return (EWOULDBLOCK);
423					return (0);
424				}
425				error = tsleep(TSA_PTC_WRITE(tp),
426						TTOPRI | PCATCH, "nmdout", 0);
427				if (error) {
428					/*
429					 * Tsleep returned (signal?).
430					 * Go find out what the user wants.
431					 * adjust for data copied in but
432					 * not written
433					 */
434					uio->uio_resid += cc;
435					return (error);
436				}
437				goto again;
438			}
439			(*linesw[tp->t_line].l_rint)(*cp++, tp);
440			cnt++;
441			cc--;
442		}
443		cc = 0;
444	}
445	return (0);
446}
447
448/*
449 * Start output on pseudo-tty.
450 * Wake up process selecting or sleeping for input from controlling tty.
451 */
452static void
453nmdmstart(struct tty *tp)
454{
455	register struct nm_softc *pti = tp->t_dev->si_drv1;
456
457	if (tp->t_state & TS_TTSTOP)
458		return;
459	pti->pt_flags &= ~PF_STOPPED;
460	wakeup_other(tp, FREAD);
461}
462
463/* Wakes up the OTHER tty;*/
464static void
465wakeup_other(struct tty *tp, int flag)
466{
467	struct softpart *ourpart, *otherpart;
468
469	GETPARTS(tp, ourpart, otherpart);
470	if (flag & FREAD) {
471		selwakeuppri(&otherpart->nm_tty.t_rsel, TTIPRI);
472		wakeup(TSA_PTC_READ((&otherpart->nm_tty)));
473	}
474	if (flag & FWRITE) {
475		selwakeuppri(&otherpart->nm_tty.t_wsel, TTOPRI);
476		wakeup(TSA_PTC_WRITE((&otherpart->nm_tty)));
477	}
478}
479
480/*
481 * stopped output on tty, called when device is closed
482 */
483static	void
484nmdmstop(register struct tty *tp, int flush)
485{
486	struct nm_softc *pti = tp->t_dev->si_drv1;
487	int flag;
488
489	/* note: FLUSHREAD and FLUSHWRITE already ok */
490	if (flush == 0) {
491		flush = TIOCPKT_STOP;
492		pti->pt_flags |= PF_STOPPED;
493	} else
494		pti->pt_flags &= ~PF_STOPPED;
495	/* change of perspective */
496	flag = 0;
497	if (flush & FREAD)
498		flag |= FWRITE;
499	if (flush & FWRITE)
500		flag |= FREAD;
501	wakeup_other(tp, flag);
502}
503
504/*
505 * handle ioctl(2) request from userland
506 */
507static	int
508nmdmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td)
509{
510	register struct tty *tp = dev->si_tty;
511	struct nm_softc *pti = dev->si_drv1;
512	int error, s;
513	register struct tty *tp2;
514	struct softpart *ourpart, *otherpart;
515
516	s = spltty();
517	GETPARTS(tp, ourpart, otherpart);
518	tp2 = &otherpart->nm_tty;
519
520	error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, td);
521	if (error == ENOIOCTL)
522		 error = ttioctl(tp, cmd, data, flag);
523	if (error == ENOIOCTL) {
524		switch (cmd) {
525		case TIOCSBRK:
526			otherpart->gotbreak = 1;
527			break;
528		case TIOCCBRK:
529			break;
530		case TIOCSDTR:
531			ourpart->modemsignals |= TIOCM_DTR;
532			break;
533		case TIOCCDTR:
534			ourpart->modemsignals &= TIOCM_DTR;
535			break;
536		case TIOCMSET:
537			ourpart->modemsignals = *(int *)data;
538			otherpart->modemsignals = *(int *)data;
539			break;
540		case TIOCMBIS:
541			ourpart->modemsignals |= *(int *)data;
542			break;
543		case TIOCMBIC:
544			ourpart->modemsignals &= ~(*(int *)data);
545			otherpart->modemsignals &= ~(*(int *)data);
546			break;
547		case TIOCMGET:
548			*(int *)data = ourpart->modemsignals;
549			break;
550		case TIOCMSDTRWAIT:
551			break;
552		case TIOCMGDTRWAIT:
553			*(int *)data = 0;
554			break;
555		case TIOCTIMESTAMP:
556			/* FALLTHROUGH */
557		case TIOCDCDTIMESTAMP:
558		default:
559			splx(s);
560			error = ENOTTY;
561			return (error);
562		}
563		error = 0;
564		nmdm_crossover(pti, ourpart, otherpart);
565	}
566	splx(s);
567	return (error);
568}
569
570static void
571nmdm_crossover(struct nm_softc *pti, struct softpart *ourpart,
572    struct softpart *otherpart)
573{
574	otherpart->modemsignals &= ~(TIOCM_CTS|TIOCM_CAR);
575	if (ourpart->modemsignals & TIOCM_RTS)
576		otherpart->modemsignals |= TIOCM_CTS;
577	if (ourpart->modemsignals & TIOCM_DTR)
578		otherpart->modemsignals |= TIOCM_CAR;
579}
580
581/*
582 * Module handling
583 */
584static int
585nmdm_modevent(module_t mod, int type, void *data)
586{
587	static eventhandler_tag tag;
588	struct nm_softc *pt, *tpt;
589        int error = 0;
590
591        switch(type) {
592        case MOD_LOAD:
593		clone_setup(&nmdmclones);
594		tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000);
595		if (tag == NULL)
596			return (ENOMEM);
597		break;
598
599	case MOD_SHUTDOWN:
600		/* FALLTHROUGH */
601	case MOD_UNLOAD:
602		EVENTHANDLER_DEREGISTER(dev_clone, tag);
603		TAILQ_FOREACH_SAFE(pt, &nmdmhead, pt_list, tpt) {
604			destroy_dev(pt->part1.dev);
605			TAILQ_REMOVE(&nmdmhead, pt, pt_list);
606			free(pt, M_NLMDM);
607		}
608		clone_cleanup(&nmdmclones);
609		break;
610	default:
611		error = EOPNOTSUPP;
612	}
613	return (error);
614}
615
616DEV_MODULE(nmdm, nmdm_modevent, NULL);
617