xref: /freebsd/sys/dev/nmdm/nmdm.c (revision f0cfa1b168014f56c02b83e5f28412cc5f78d117)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1982, 1986, 1989, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  */
32 
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35 
36 /*
37  * Pseudo-nulmodem driver
38  * Mighty handy for use with serial console in Vmware
39  */
40 
41 #include <sys/param.h>
42 #include <sys/systm.h>
43 #include <sys/priv.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/limits.h>
51 #include <sys/module.h>
52 #include <sys/serial.h>
53 #include <sys/signalvar.h>
54 #include <sys/malloc.h>
55 #include <sys/taskqueue.h>
56 
57 static MALLOC_DEFINE(M_NMDM, "nullmodem", "nullmodem data structures");
58 
59 static tsw_inwakeup_t	nmdm_outwakeup;
60 static tsw_outwakeup_t	nmdm_inwakeup;
61 static tsw_param_t	nmdm_param;
62 static tsw_modem_t	nmdm_modem;
63 static tsw_close_t	nmdm_close;
64 static tsw_free_t	nmdm_free;
65 
66 static struct ttydevsw nmdm_class = {
67 	.tsw_flags	= TF_NOPREFIX,
68 	.tsw_inwakeup	= nmdm_inwakeup,
69 	.tsw_outwakeup	= nmdm_outwakeup,
70 	.tsw_param	= nmdm_param,
71 	.tsw_modem	= nmdm_modem,
72 	.tsw_close	= nmdm_close,
73 	.tsw_free	= nmdm_free,
74 };
75 
76 static void nmdm_task_tty(void *, int);
77 
78 struct nmdmsoftc;
79 
80 struct nmdmpart {
81 	struct tty		*np_tty;
82 	int			 np_dcd;
83 	struct task		 np_task;
84 	struct nmdmpart		*np_other;
85 	struct nmdmsoftc	*np_pair;
86 	struct callout		 np_callout;
87 	u_long			 np_quota;
88 	u_long			 np_accumulator;
89 	int			 np_rate;
90 	int			 np_credits;
91 
92 #define QS 8	/* Quota shift */
93 };
94 
95 struct nmdmsoftc {
96 	struct nmdmpart	ns_part1;
97 	struct nmdmpart	ns_part2;
98 	struct mtx	ns_mtx;
99 };
100 
101 static int nmdm_count = 0;
102 
103 static void
104 nmdm_close(struct tty *tp)
105 {
106 	struct nmdmpart *np;
107 	struct nmdmpart *onp;
108 	struct tty *otp;
109 
110 	np = tty_softc(tp);
111 	onp = np->np_other;
112 	otp = onp->np_tty;
113 
114 	/* If second part is opened, do not destroy ourselves. */
115 	if (tty_opened(otp))
116 		return;
117 
118 	/* Shut down self. */
119 	tty_rel_gone(tp);
120 
121 	/* Shut down second part. */
122 	tty_lock(tp);
123 	onp = np->np_other;
124 	if (onp == NULL)
125 		return;
126 	otp = onp->np_tty;
127 	tty_rel_gone(otp);
128 	tty_lock(tp);
129 }
130 
131 static void
132 nmdm_free(void *softc)
133 {
134 	struct nmdmpart *np = (struct nmdmpart *)softc;
135 	struct nmdmsoftc *ns = np->np_pair;
136 
137 	callout_drain(&np->np_callout);
138 	taskqueue_drain(taskqueue_swi, &np->np_task);
139 
140 	/*
141 	 * The function is called on both parts simultaneously.  We serialize
142 	 * with help of ns_mtx.  The first invocation should return and
143 	 * delegate freeing of resources to the second.
144 	 */
145 	mtx_lock(&ns->ns_mtx);
146 	if (np->np_other != NULL) {
147 		np->np_other->np_other = NULL;
148 		mtx_unlock(&ns->ns_mtx);
149 		return;
150 	}
151 	mtx_destroy(&ns->ns_mtx);
152 	free(ns, M_NMDM);
153 	atomic_subtract_int(&nmdm_count, 1);
154 }
155 
156 static void
157 nmdm_clone(void *arg, struct ucred *cred, char *name, int nameen,
158     struct cdev **dev)
159 {
160 	struct nmdmsoftc *ns;
161 	struct tty *tp;
162 	char *end;
163 	int error;
164 	char endc;
165 
166 	if (*dev != NULL)
167 		return;
168 	if (strncmp(name, "nmdm", 4) != 0)
169 		return;
170 	if (strlen(name) <= strlen("nmdmX"))
171 		return;
172 
173 	/* Device name must be "nmdm%s%c", where %c is 'A' or 'B'. */
174 	end = name + strlen(name) - 1;
175 	endc = *end;
176 	if (endc != 'A' && endc != 'B')
177 		return;
178 
179 	ns = malloc(sizeof(*ns), M_NMDM, M_WAITOK | M_ZERO);
180 	mtx_init(&ns->ns_mtx, "nmdm", NULL, MTX_DEF);
181 
182 	/* Hook the pairs together. */
183 	ns->ns_part1.np_pair = ns;
184 	ns->ns_part1.np_other = &ns->ns_part2;
185 	TASK_INIT(&ns->ns_part1.np_task, 0, nmdm_task_tty, &ns->ns_part1);
186 	callout_init_mtx(&ns->ns_part1.np_callout, &ns->ns_mtx, 0);
187 
188 	ns->ns_part2.np_pair = ns;
189 	ns->ns_part2.np_other = &ns->ns_part1;
190 	TASK_INIT(&ns->ns_part2.np_task, 0, nmdm_task_tty, &ns->ns_part2);
191 	callout_init_mtx(&ns->ns_part2.np_callout, &ns->ns_mtx, 0);
192 
193 	/* Create device nodes. */
194 	tp = ns->ns_part1.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part1,
195 	    &ns->ns_mtx);
196 	*end = 'A';
197 	error = tty_makedevf(tp, NULL, endc == 'A' ? TTYMK_CLONING : 0,
198 	    "%s", name);
199 	if (error) {
200 		*end = endc;
201 		mtx_destroy(&ns->ns_mtx);
202 		free(ns, M_NMDM);
203 		return;
204 	}
205 
206 	tp = ns->ns_part2.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part2,
207 	    &ns->ns_mtx);
208 	*end = 'B';
209 	error = tty_makedevf(tp, NULL, endc == 'B' ? TTYMK_CLONING : 0,
210 	    "%s", name);
211 	if (error) {
212 		*end = endc;
213 		mtx_lock(&ns->ns_mtx);
214 		/* see nmdm_free() */
215 		ns->ns_part1.np_other = NULL;
216 		atomic_add_int(&nmdm_count, 1);
217 		tty_rel_gone(ns->ns_part1.np_tty);
218 		return;
219 	}
220 
221 	if (endc == 'A')
222 		*dev = ns->ns_part1.np_tty->t_dev;
223 	else
224 		*dev = ns->ns_part2.np_tty->t_dev;
225 
226 	*end = endc;
227 	atomic_add_int(&nmdm_count, 1);
228 }
229 
230 static void
231 nmdm_timeout(void *arg)
232 {
233 	struct nmdmpart *np = arg;
234 
235 	if (np->np_rate == 0)
236 		return;
237 
238 	/*
239 	 * Do a simple Floyd-Steinberg dither here to avoid FP math.
240 	 * Wipe out unused quota from last tick.
241 	 */
242 	np->np_accumulator += np->np_credits;
243 	np->np_quota = np->np_accumulator >> QS;
244 	np->np_accumulator &= ((1 << QS) - 1);
245 
246 	taskqueue_enqueue(taskqueue_swi, &np->np_task);
247 	callout_reset(&np->np_callout, np->np_rate, nmdm_timeout, np);
248 }
249 
250 static void
251 nmdm_task_tty(void *arg, int pending __unused)
252 {
253 	struct tty *tp, *otp;
254 	struct nmdmpart *np = arg;
255 	char c;
256 
257 	tp = np->np_tty;
258 	tty_lock(tp);
259 	if (tty_gone(tp)) {
260 		tty_unlock(tp);
261 		return;
262 	}
263 
264 	otp = np->np_other->np_tty;
265 	KASSERT(otp != NULL, ("NULL otp in nmdmstart"));
266 	KASSERT(otp != tp, ("NULL otp == tp nmdmstart"));
267 	if (np->np_other->np_dcd) {
268 		if (!tty_opened(tp)) {
269 			np->np_other->np_dcd = 0;
270 			ttydisc_modem(otp, 0);
271 		}
272 	} else {
273 		if (tty_opened(tp)) {
274 			np->np_other->np_dcd = 1;
275 			ttydisc_modem(otp, 1);
276 		}
277 	}
278 
279 	/* This may happen when we are in detach process. */
280 	if (tty_gone(otp)) {
281 		tty_unlock(otp);
282 		return;
283 	}
284 
285 	while (ttydisc_rint_poll(otp) > 0) {
286 		if (np->np_rate && !np->np_quota)
287 			break;
288 		if (ttydisc_getc(tp, &c, 1) != 1)
289 			break;
290 		np->np_quota--;
291 		ttydisc_rint(otp, c, 0);
292 	}
293 
294 	ttydisc_rint_done(otp);
295 
296 	tty_unlock(tp);
297 }
298 
299 static int
300 bits_per_char(struct termios *t)
301 {
302 	int bits;
303 
304 	bits = 1;		/* start bit */
305 	switch (t->c_cflag & CSIZE) {
306 	case CS5:	bits += 5;	break;
307 	case CS6:	bits += 6;	break;
308 	case CS7:	bits += 7;	break;
309 	case CS8:	bits += 8;	break;
310 	}
311 	bits++;			/* stop bit */
312 	if (t->c_cflag & PARENB)
313 		bits++;
314 	if (t->c_cflag & CSTOPB)
315 		bits++;
316 	return (bits);
317 }
318 
319 static int
320 nmdm_param(struct tty *tp, struct termios *t)
321 {
322 	struct nmdmpart *np = tty_softc(tp);
323 	struct tty *tp2;
324 	int bpc, rate, speed, i;
325 
326 	tp2 = np->np_other->np_tty;
327 
328 	if (!((t->c_cflag | tp2->t_termios.c_cflag) & CDSR_OFLOW)) {
329 		np->np_rate = 0;
330 		np->np_other->np_rate = 0;
331 		return (0);
332 	}
333 
334 	/*
335 	 * DSRFLOW one either side enables rate-simulation for both
336 	 * directions.
337 	 * NB: the two directions may run at different rates.
338 	 */
339 
340 	/* Find the larger of the number of bits transmitted */
341 	bpc = imax(bits_per_char(t), bits_per_char(&tp2->t_termios));
342 
343 	for (i = 0; i < 2; i++) {
344 		/* Use the slower of our receive and their transmit rate */
345 		speed = imin(tp2->t_termios.c_ospeed, t->c_ispeed);
346 		if (speed == 0) {
347 			np->np_rate = 0;
348 			np->np_other->np_rate = 0;
349 			return (0);
350 		}
351 
352 		speed <<= QS;			/* [bit/sec, scaled] */
353 		speed /= bpc;			/* [char/sec, scaled] */
354 		rate = (hz << QS) / speed;	/* [hz per callout] */
355 		if (rate == 0)
356 			rate = 1;
357 
358 		speed *= rate;
359 		speed /= hz;			/* [(char/sec)/tick, scaled */
360 
361 		np->np_credits = speed;
362 		np->np_rate = rate;
363 		callout_reset(&np->np_callout, rate, nmdm_timeout, np);
364 
365 		/*
366 		 * swap pointers for second pass so the other end gets
367 		 * updated as well.
368 		 */
369 		np = np->np_other;
370 		t = &tp2->t_termios;
371 		tp2 = tp;
372 	}
373 
374 	return (0);
375 }
376 
377 static int
378 nmdm_modem(struct tty *tp, int sigon, int sigoff)
379 {
380 	struct nmdmpart *np = tty_softc(tp);
381 	int i = 0;
382 
383 	if (sigon || sigoff) {
384 		if (sigon & SER_DTR)
385 			np->np_other->np_dcd = 1;
386 		if (sigoff & SER_DTR)
387 			np->np_other->np_dcd = 0;
388 
389 		ttydisc_modem(np->np_other->np_tty, np->np_other->np_dcd);
390 
391 		return (0);
392 	} else {
393 		if (np->np_dcd)
394 			i |= SER_DCD;
395 		if (np->np_other->np_dcd)
396 			i |= SER_DTR;
397 
398 		return (i);
399 	}
400 }
401 
402 static void
403 nmdm_inwakeup(struct tty *tp)
404 {
405 	struct nmdmpart *np = tty_softc(tp);
406 
407 	/* We can receive again, so wake up the other side. */
408 	taskqueue_enqueue(taskqueue_swi, &np->np_other->np_task);
409 }
410 
411 static void
412 nmdm_outwakeup(struct tty *tp)
413 {
414 	struct nmdmpart *np = tty_softc(tp);
415 
416 	/* We can transmit again, so wake up our side. */
417 	taskqueue_enqueue(taskqueue_swi, &np->np_task);
418 }
419 
420 /*
421  * Module handling
422  */
423 static int
424 nmdm_modevent(module_t mod, int type, void *data)
425 {
426 	static eventhandler_tag tag;
427 
428         switch(type) {
429         case MOD_LOAD:
430 		tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000);
431 		if (tag == NULL)
432 			return (ENOMEM);
433 		break;
434 
435 	case MOD_SHUTDOWN:
436 		break;
437 
438 	case MOD_UNLOAD:
439 		if (nmdm_count != 0)
440 			return (EBUSY);
441 		EVENTHANDLER_DEREGISTER(dev_clone, tag);
442 		break;
443 
444 	default:
445 		return (EOPNOTSUPP);
446 	}
447 
448 	return (0);
449 }
450 
451 DEV_MODULE(nmdm, nmdm_modevent, NULL);
452