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