xref: /freebsd/sys/dev/nmdm/nmdm.c (revision 822923447e454b30d310cb46903c9ddeca9f0a7a)
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$");
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 #include <sys/proc.h>
45 #include <sys/tty.h>
46 #include <sys/conf.h>
47 #include <sys/fcntl.h>
48 #include <sys/poll.h>
49 #include <sys/kernel.h>
50 #include <sys/module.h>
51 #include <sys/serial.h>
52 #include <sys/signalvar.h>
53 #include <sys/malloc.h>
54 #include <sys/taskqueue.h>
55 
56 MALLOC_DEFINE(M_NLMDM, "nullmodem", "nullmodem data structures");
57 
58 static d_close_t	nmdmclose;
59 static t_modem_t	nmdmmodem;
60 static d_open_t		nmdmopen;
61 static t_oproc_t	nmdmoproc;
62 static t_param_t	nmdmparam;
63 static t_stop_t		nmdmstop;
64 
65 static void 	nmdminit(struct cdev *dev);
66 
67 
68 static struct cdevsw nmdm_cdevsw = {
69 	.d_version =	D_VERSION,
70 	.d_open =	nmdmopen,
71 	.d_close =	nmdmclose,
72 	.d_name =	"nmdn",
73 	.d_flags =	D_TTY | D_PSEUDO | D_NEEDGIANT,
74 };
75 
76 #define BUFSIZ 		100		/* Chunk size iomoved to/from user */
77 #define NMDM_MAX_NUM	128		/* Artificially limit # devices. */
78 #define	PF_STOPPED	0x10		/* user told stopped */
79 #define BFLAG		CLONE_FLAG0
80 
81 struct softpart {
82 	struct tty		*nm_tty;
83 	struct cdev 		*dev;
84 	int			nm_dcd;
85 	struct task		pt_task;
86 	struct softpart		*other;
87 	struct callout		co;
88 	u_long			quota;
89 	u_long			accumulator;
90 	int			rate;
91 	int			credits;
92 
93 #define QS 8	/* Quota shift */
94 };
95 
96 struct	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 
103 static struct clonedevs *nmdmclones;
104 static TAILQ_HEAD(,nm_softc) nmdmhead = TAILQ_HEAD_INITIALIZER(nmdmhead);
105 
106 static void
107 nmdm_clone(void *arg, struct ucred *cred, char *name, int nameen,
108     struct cdev **dev)
109 {
110 	int i, unit;
111 	char *p;
112 	struct cdev *d1, *d2;
113 
114 	if (*dev != NULL)
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 	dev_ref(*dev);
152 }
153 
154 static void
155 nmdm_timeout(void *arg)
156 {
157 	struct softpart *sp;
158 
159 	sp = arg;
160 
161 	if (sp->rate == 0)
162 		return;
163 
164 	/*
165 	 * Do a simple Floyd-Steinberg dither here to avoid FP math.
166 	 * Wipe out unused quota from last tick.
167 	 */
168 	sp->accumulator += sp->credits;
169 	sp->quota = sp->accumulator >> QS;
170 	sp->accumulator &= ((1 << QS) - 1);
171 
172 	taskqueue_enqueue(taskqueue_swi_giant, &sp->pt_task);
173 	callout_reset(&sp->co, sp->rate, nmdm_timeout, arg);
174 }
175 
176 static void
177 nmdm_task_tty(void *arg, int pending __unused)
178 {
179 	struct tty *tp, *otp;
180 	struct softpart *sp;
181 	int c;
182 
183 	tp = arg;
184 	sp = tp->t_sc;
185 	otp = sp->other->nm_tty;
186 	KASSERT(otp != NULL, ("NULL otp in nmdmstart"));
187 	KASSERT(otp != tp, ("NULL otp == tp nmdmstart"));
188 	if (sp->other->nm_dcd) {
189 		if (!(tp->t_state & TS_ISOPEN)) {
190 			sp->other->nm_dcd = 0;
191 			(void)ttyld_modem(otp, 0);
192 		}
193 	} else {
194 		if (tp->t_state & TS_ISOPEN) {
195 			sp->other->nm_dcd = 1;
196 			(void)ttyld_modem(otp, 1);
197 		}
198 	}
199 	if (tp->t_state & TS_TTSTOP)
200 		return;
201 	while (tp->t_outq.c_cc != 0) {
202 		if (sp->rate && !sp->quota)
203 			return;
204 		if (otp->t_state & TS_TBLOCK)
205 			return;
206 		sp->quota--;
207 		c = getc(&tp->t_outq);
208 		if (otp->t_state & TS_ISOPEN)
209 			ttyld_rint(otp, c);
210 	}
211 	if (tp->t_outq.c_cc == 0)
212 		ttwwakeup(tp);
213 
214 }
215 
216 /*
217  * This function creates and initializes a pair of ttys.
218  */
219 static void
220 nmdminit(struct cdev *dev1)
221 {
222 	struct cdev *dev2;
223 	struct nm_softc *pt;
224 
225 	dev2 = dev1->si_drv2;
226 
227 	dev1->si_flags &= ~SI_CHEAPCLONE;
228 	dev2->si_flags &= ~SI_CHEAPCLONE;
229 
230 	pt = malloc(sizeof(*pt), M_NLMDM, M_WAITOK | M_ZERO);
231 	TAILQ_INSERT_TAIL(&nmdmhead, pt, pt_list);
232 
233 	dev1->si_drv1 = dev2->si_drv1 = pt;
234 
235 	pt->part1.dev = dev1;
236 	pt->part2.dev = dev2;
237 
238 	pt->part1.nm_tty = ttymalloc(pt->part1.nm_tty);
239 	pt->part1.nm_tty->t_oproc = nmdmoproc;
240 	pt->part1.nm_tty->t_stop = nmdmstop;
241 	pt->part1.nm_tty->t_modem = nmdmmodem;
242 	pt->part1.nm_tty->t_param = nmdmparam;
243 	pt->part1.nm_tty->t_dev = dev1;
244 	pt->part1.nm_tty->t_sc = &pt->part1;
245 	TASK_INIT(&pt->part1.pt_task, 0, nmdm_task_tty, pt->part1.nm_tty);
246 	callout_init(&pt->part1.co, 0);
247 
248 	pt->part2.nm_tty = ttymalloc(pt->part2.nm_tty);
249 	pt->part2.nm_tty->t_oproc = nmdmoproc;
250 	pt->part2.nm_tty->t_stop = nmdmstop;
251 	pt->part2.nm_tty->t_modem = nmdmmodem;
252 	pt->part2.nm_tty->t_param = nmdmparam;
253 	pt->part2.nm_tty->t_dev = dev2;
254 	pt->part2.nm_tty->t_sc = &pt->part2;
255 	TASK_INIT(&pt->part2.pt_task, 0, nmdm_task_tty, pt->part2.nm_tty);
256 	callout_init(&pt->part2.co, 0);
257 
258 	pt->part1.other = &pt->part2;
259 	pt->part2.other = &pt->part1;
260 
261 	dev1->si_tty = pt->part1.nm_tty;
262 	dev1->si_drv1 = pt;
263 
264 	dev2->si_tty = pt->part2.nm_tty;
265 	dev2->si_drv1 = pt;
266 }
267 
268 /*
269  * Device opened from userland
270  */
271 static	int
272 nmdmopen(struct cdev *dev, int flag, int devtype, struct thread *td)
273 {
274 	struct tty *tp, *tp2;
275 	int error;
276 	struct nm_softc *pti;
277 	struct softpart *sp;
278 
279 	if (dev->si_drv1 == NULL)
280 		nmdminit(dev);
281 	pti = dev->si_drv1;
282 	if (pti->pt_prison != td->td_ucred->cr_prison)
283 		return (EBUSY);
284 
285 	tp = dev->si_tty;
286 	sp = tp->t_sc;
287 	tp2 = sp->other->nm_tty;
288 
289 	if ((tp->t_state & TS_ISOPEN) == 0) {
290 		ttyinitmode(tp, 0, 0);
291 		ttsetwater(tp); /* XXX ? */
292 	} else if (tp->t_state & TS_XCLUDE && suser(td)) {
293 		return (EBUSY);
294 	}
295 
296 	error = ttyld_open(tp, dev);
297 	return (error);
298 }
299 
300 static int
301 bits_per_char(struct termios *t)
302 {
303 	int bits;
304 
305 	bits = 1;		/* start bit */
306 	switch (t->c_cflag & CSIZE) {
307 	case CS5:	bits += 5;	break;
308 	case CS6:	bits += 6;	break;
309 	case CS7:	bits += 7;	break;
310 	case CS8:	bits += 8;	break;
311 	}
312 	bits++;			/* stop bit */
313 	if (t->c_cflag & PARENB)
314 		bits++;
315 	if (t->c_cflag & CSTOPB)
316 		bits++;
317 	return (bits);
318 }
319 
320 static int
321 nmdmparam(struct tty *tp, struct termios *t)
322 {
323 	struct softpart *sp;
324 	struct tty *tp2;
325 	int bpc, rate, speed, i;
326 
327 	sp = tp->t_sc;
328 	tp2 = sp->other->nm_tty;
329 
330 	if (!((t->c_cflag | tp2->t_cflag) & CDSR_OFLOW)) {
331 		sp->rate = 0;
332 		sp->other->rate = 0;
333 		return (0);
334 	}
335 
336 	/*
337 	 * DSRFLOW one either side enables rate-simulation for both
338 	 * directions.
339 	 * NB: the two directions may run at different rates.
340 	 */
341 
342 	/* Find the larger of the number of bits transmitted */
343 	bpc = imax(bits_per_char(t), bits_per_char(&tp2->t_termios));
344 
345 	for (i = 0; i < 2; i++) {
346 		/* Use the slower of our receive and their transmit rate */
347 		speed = imin(tp2->t_ospeed, t->c_ispeed);
348 		if (speed == 0) {
349 			sp->rate = 0;
350 			sp->other->rate = 0;
351 			return (0);
352 		}
353 
354 		speed <<= QS;			/* [bit/sec, scaled] */
355 		speed /= bpc;			/* [char/sec, scaled] */
356 		rate = (hz << QS) / speed;	/* [hz per callout] */
357 		if (rate == 0)
358 			rate = 1;
359 
360 		speed *= rate;
361 		speed /= hz;			/* [(char/sec)/tick, scaled */
362 
363 		sp->credits = speed;
364 		sp->rate = rate;
365 		callout_reset(&sp->co, rate, nmdm_timeout, sp);
366 
367 		/*
368 		 * swap pointers for second pass so the other end gets
369 		 * updated as well.
370 		 */
371 		sp = sp->other;
372 		t = &tp2->t_termios;
373 		tp2 = tp;
374 	}
375 	return (0);
376 }
377 
378 static int
379 nmdmmodem(struct tty *tp, int sigon, int sigoff)
380 {
381 	struct softpart *sp;
382 	int i;
383 
384 	sp = tp->t_sc;
385 	if (sigon || sigoff) {
386 		if (sigon & SER_DTR)
387 			sp->other->nm_dcd = 1;
388 		if (sigoff & SER_DTR)
389 			sp->other->nm_dcd = 0;
390 		ttyld_modem(sp->other->nm_tty, sp->other->nm_dcd);
391 		return (0);
392 	} else {
393 		i = 0;
394 		if (sp->nm_dcd)
395 			i |= SER_DCD;
396 		if (sp->other->nm_dcd)
397 			i |= SER_DTR;
398 		return (i);
399 	}
400 }
401 
402 static int
403 nmdmclose(struct cdev *dev, int flag, int mode, struct thread *td)
404 {
405 
406 	return (tty_close(dev->si_tty));
407 }
408 
409 static void
410 nmdmoproc(struct tty *tp)
411 {
412 	struct softpart *pt;
413 
414 	pt = tp->t_sc;
415 	taskqueue_enqueue(taskqueue_swi_giant, &pt->pt_task);
416 }
417 
418 static void
419 nmdmstop(struct tty *tp, int flush)
420 {
421 	struct softpart *pt;
422 
423 	pt = tp->t_sc;
424 	taskqueue_enqueue(taskqueue_swi_giant, &pt->pt_task);
425 }
426 
427 /*
428  * Module handling
429  */
430 static int
431 nmdm_modevent(module_t mod, int type, void *data)
432 {
433 	static eventhandler_tag tag;
434 	struct nm_softc *pt, *tpt;
435         int error = 0;
436 
437         switch(type) {
438         case MOD_LOAD:
439 		clone_setup(&nmdmclones);
440 		tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000);
441 		if (tag == NULL)
442 			return (ENOMEM);
443 		break;
444 
445 	case MOD_SHUTDOWN:
446 		/* FALLTHROUGH */
447 	case MOD_UNLOAD:
448 		EVENTHANDLER_DEREGISTER(dev_clone, tag);
449 		TAILQ_FOREACH_SAFE(pt, &nmdmhead, pt_list, tpt) {
450 			destroy_dev(pt->part1.dev);
451 			TAILQ_REMOVE(&nmdmhead, pt, pt_list);
452 			free(pt, M_NLMDM);
453 		}
454 		clone_cleanup(&nmdmclones);
455 		break;
456 	default:
457 		error = EOPNOTSUPP;
458 	}
459 	return (error);
460 }
461 
462 DEV_MODULE(nmdm, nmdm_modevent, NULL);
463