xref: /freebsd/sys/dev/nmdm/nmdm.c (revision 732a02b4e77866604a120a275c082bb6221bd2ff)
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/eventhandler.h>
48 #include <sys/fcntl.h>
49 #include <sys/poll.h>
50 #include <sys/kernel.h>
51 #include <sys/limits.h>
52 #include <sys/module.h>
53 #include <sys/serial.h>
54 #include <sys/signalvar.h>
55 #include <sys/malloc.h>
56 #include <sys/taskqueue.h>
57 
58 static MALLOC_DEFINE(M_NMDM, "nullmodem", "nullmodem data structures");
59 
60 static tsw_inwakeup_t	nmdm_outwakeup;
61 static tsw_outwakeup_t	nmdm_inwakeup;
62 static tsw_param_t	nmdm_param;
63 static tsw_modem_t	nmdm_modem;
64 static tsw_close_t	nmdm_close;
65 static tsw_free_t	nmdm_free;
66 
67 static struct ttydevsw nmdm_class = {
68 	.tsw_flags	= TF_NOPREFIX,
69 	.tsw_inwakeup	= nmdm_inwakeup,
70 	.tsw_outwakeup	= nmdm_outwakeup,
71 	.tsw_param	= nmdm_param,
72 	.tsw_modem	= nmdm_modem,
73 	.tsw_close	= nmdm_close,
74 	.tsw_free	= nmdm_free,
75 };
76 
77 static void nmdm_task_tty(void *, int);
78 
79 struct nmdmsoftc;
80 
81 struct nmdmpart {
82 	struct tty		*np_tty;
83 	int			 np_dcd;
84 	struct task		 np_task;
85 	struct nmdmpart		*np_other;
86 	struct nmdmsoftc	*np_pair;
87 	struct callout		 np_callout;
88 	u_long			 np_quota;
89 	u_long			 np_accumulator;
90 	int			 np_rate;
91 	int			 np_credits;
92 
93 #define QS 8	/* Quota shift */
94 };
95 
96 struct nmdmsoftc {
97 	struct nmdmpart	ns_part1;
98 	struct nmdmpart	ns_part2;
99 	struct mtx	ns_mtx;
100 };
101 
102 static int nmdm_count = 0;
103 
104 static void
105 nmdm_close(struct tty *tp)
106 {
107 	struct nmdmpart *np;
108 	struct nmdmpart *onp;
109 	struct tty *otp;
110 
111 	np = tty_softc(tp);
112 	onp = np->np_other;
113 	otp = onp->np_tty;
114 
115 	/* If second part is opened, do not destroy ourselves. */
116 	if (tty_opened(otp))
117 		return;
118 
119 	/* Shut down self. */
120 	tty_rel_gone(tp);
121 
122 	/* Shut down second part. */
123 	tty_lock(tp);
124 	onp = np->np_other;
125 	if (onp == NULL)
126 		return;
127 	otp = onp->np_tty;
128 	tty_rel_gone(otp);
129 	tty_lock(tp);
130 }
131 
132 static void
133 nmdm_free(void *softc)
134 {
135 	struct nmdmpart *np = (struct nmdmpart *)softc;
136 	struct nmdmsoftc *ns = np->np_pair;
137 
138 	callout_drain(&np->np_callout);
139 	taskqueue_drain(taskqueue_swi, &np->np_task);
140 
141 	/*
142 	 * The function is called on both parts simultaneously.  We serialize
143 	 * with help of ns_mtx.  The first invocation should return and
144 	 * delegate freeing of resources to the second.
145 	 */
146 	mtx_lock(&ns->ns_mtx);
147 	if (np->np_other != NULL) {
148 		np->np_other->np_other = NULL;
149 		mtx_unlock(&ns->ns_mtx);
150 		return;
151 	}
152 	mtx_destroy(&ns->ns_mtx);
153 	free(ns, M_NMDM);
154 	atomic_subtract_int(&nmdm_count, 1);
155 }
156 
157 static void
158 nmdm_clone(void *arg, struct ucred *cred, char *name, int nameen,
159     struct cdev **dev)
160 {
161 	struct nmdmsoftc *ns;
162 	struct tty *tp;
163 	char *end;
164 	int error;
165 	char endc;
166 
167 	if (*dev != NULL)
168 		return;
169 	if (strncmp(name, "nmdm", 4) != 0)
170 		return;
171 	if (strlen(name) <= strlen("nmdmX"))
172 		return;
173 
174 	/* Device name must be "nmdm%s%c", where %c is 'A' or 'B'. */
175 	end = name + strlen(name) - 1;
176 	endc = *end;
177 	if (endc != 'A' && endc != 'B')
178 		return;
179 
180 	ns = malloc(sizeof(*ns), M_NMDM, M_WAITOK | M_ZERO);
181 	mtx_init(&ns->ns_mtx, "nmdm", NULL, MTX_DEF);
182 
183 	/* Hook the pairs together. */
184 	ns->ns_part1.np_pair = ns;
185 	ns->ns_part1.np_other = &ns->ns_part2;
186 	TASK_INIT(&ns->ns_part1.np_task, 0, nmdm_task_tty, &ns->ns_part1);
187 	callout_init_mtx(&ns->ns_part1.np_callout, &ns->ns_mtx, 0);
188 
189 	ns->ns_part2.np_pair = ns;
190 	ns->ns_part2.np_other = &ns->ns_part1;
191 	TASK_INIT(&ns->ns_part2.np_task, 0, nmdm_task_tty, &ns->ns_part2);
192 	callout_init_mtx(&ns->ns_part2.np_callout, &ns->ns_mtx, 0);
193 
194 	/* Create device nodes. */
195 	tp = ns->ns_part1.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part1,
196 	    &ns->ns_mtx);
197 	*end = 'A';
198 	error = tty_makedevf(tp, NULL, endc == 'A' ? TTYMK_CLONING : 0,
199 	    "%s", name);
200 	if (error) {
201 		*end = endc;
202 		mtx_destroy(&ns->ns_mtx);
203 		free(ns, M_NMDM);
204 		return;
205 	}
206 
207 	tp = ns->ns_part2.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part2,
208 	    &ns->ns_mtx);
209 	*end = 'B';
210 	error = tty_makedevf(tp, NULL, endc == 'B' ? TTYMK_CLONING : 0,
211 	    "%s", name);
212 	if (error) {
213 		*end = endc;
214 		mtx_lock(&ns->ns_mtx);
215 		/* see nmdm_free() */
216 		ns->ns_part1.np_other = NULL;
217 		atomic_add_int(&nmdm_count, 1);
218 		tty_rel_gone(ns->ns_part1.np_tty);
219 		return;
220 	}
221 
222 	if (endc == 'A')
223 		*dev = ns->ns_part1.np_tty->t_dev;
224 	else
225 		*dev = ns->ns_part2.np_tty->t_dev;
226 
227 	*end = endc;
228 	atomic_add_int(&nmdm_count, 1);
229 }
230 
231 static void
232 nmdm_timeout(void *arg)
233 {
234 	struct nmdmpart *np = arg;
235 
236 	if (np->np_rate == 0)
237 		return;
238 
239 	/*
240 	 * Do a simple Floyd-Steinberg dither here to avoid FP math.
241 	 * Wipe out unused quota from last tick.
242 	 */
243 	np->np_accumulator += np->np_credits;
244 	np->np_quota = np->np_accumulator >> QS;
245 	np->np_accumulator &= ((1 << QS) - 1);
246 
247 	taskqueue_enqueue(taskqueue_swi, &np->np_task);
248 	callout_reset(&np->np_callout, np->np_rate, nmdm_timeout, np);
249 }
250 
251 static void
252 nmdm_task_tty(void *arg, int pending __unused)
253 {
254 	struct tty *tp, *otp;
255 	struct nmdmpart *np = arg;
256 	char c;
257 
258 	tp = np->np_tty;
259 	tty_lock(tp);
260 	if (tty_gone(tp)) {
261 		tty_unlock(tp);
262 		return;
263 	}
264 
265 	otp = np->np_other->np_tty;
266 	KASSERT(otp != NULL, ("NULL otp in nmdmstart"));
267 	KASSERT(otp != tp, ("NULL otp == tp nmdmstart"));
268 	if (np->np_other->np_dcd) {
269 		if (!tty_opened(tp)) {
270 			np->np_other->np_dcd = 0;
271 			ttydisc_modem(otp, 0);
272 		}
273 	} else {
274 		if (tty_opened(tp)) {
275 			np->np_other->np_dcd = 1;
276 			ttydisc_modem(otp, 1);
277 		}
278 	}
279 
280 	/* This may happen when we are in detach process. */
281 	if (tty_gone(otp)) {
282 		tty_unlock(otp);
283 		return;
284 	}
285 
286 	while (ttydisc_rint_poll(otp) > 0) {
287 		if (np->np_rate && !np->np_quota)
288 			break;
289 		if (ttydisc_getc(tp, &c, 1) != 1)
290 			break;
291 		np->np_quota--;
292 		ttydisc_rint(otp, c, 0);
293 	}
294 
295 	ttydisc_rint_done(otp);
296 
297 	tty_unlock(tp);
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 nmdm_param(struct tty *tp, struct termios *t)
322 {
323 	struct nmdmpart *np = tty_softc(tp);
324 	struct tty *tp2;
325 	int bpc, rate, speed, i;
326 
327 	tp2 = np->np_other->np_tty;
328 
329 	if (!((t->c_cflag | tp2->t_termios.c_cflag) & CDSR_OFLOW)) {
330 		np->np_rate = 0;
331 		np->np_other->np_rate = 0;
332 		return (0);
333 	}
334 
335 	/*
336 	 * DSRFLOW one either side enables rate-simulation for both
337 	 * directions.
338 	 * NB: the two directions may run at different rates.
339 	 */
340 
341 	/* Find the larger of the number of bits transmitted */
342 	bpc = imax(bits_per_char(t), bits_per_char(&tp2->t_termios));
343 
344 	for (i = 0; i < 2; i++) {
345 		/* Use the slower of our receive and their transmit rate */
346 		speed = imin(tp2->t_termios.c_ospeed, t->c_ispeed);
347 		if (speed == 0) {
348 			np->np_rate = 0;
349 			np->np_other->np_rate = 0;
350 			return (0);
351 		}
352 
353 		speed <<= QS;			/* [bit/sec, scaled] */
354 		speed /= bpc;			/* [char/sec, scaled] */
355 		rate = (hz << QS) / speed;	/* [hz per callout] */
356 		if (rate == 0)
357 			rate = 1;
358 
359 		speed *= rate;
360 		speed /= hz;			/* [(char/sec)/tick, scaled */
361 
362 		np->np_credits = speed;
363 		np->np_rate = rate;
364 		callout_reset(&np->np_callout, rate, nmdm_timeout, np);
365 
366 		/*
367 		 * swap pointers for second pass so the other end gets
368 		 * updated as well.
369 		 */
370 		np = np->np_other;
371 		t = &tp2->t_termios;
372 		tp2 = tp;
373 	}
374 
375 	return (0);
376 }
377 
378 static int
379 nmdm_modem(struct tty *tp, int sigon, int sigoff)
380 {
381 	struct nmdmpart *np = tty_softc(tp);
382 	int i = 0;
383 
384 	if (sigon || sigoff) {
385 		if (sigon & SER_DTR)
386 			np->np_other->np_dcd = 1;
387 		if (sigoff & SER_DTR)
388 			np->np_other->np_dcd = 0;
389 
390 		ttydisc_modem(np->np_other->np_tty, np->np_other->np_dcd);
391 
392 		return (0);
393 	} else {
394 		if (np->np_dcd)
395 			i |= SER_DCD;
396 		if (np->np_other->np_dcd)
397 			i |= SER_DTR;
398 
399 		return (i);
400 	}
401 }
402 
403 static void
404 nmdm_inwakeup(struct tty *tp)
405 {
406 	struct nmdmpart *np = tty_softc(tp);
407 
408 	/* We can receive again, so wake up the other side. */
409 	taskqueue_enqueue(taskqueue_swi, &np->np_other->np_task);
410 }
411 
412 static void
413 nmdm_outwakeup(struct tty *tp)
414 {
415 	struct nmdmpart *np = tty_softc(tp);
416 
417 	/* We can transmit again, so wake up our side. */
418 	taskqueue_enqueue(taskqueue_swi, &np->np_task);
419 }
420 
421 /*
422  * Module handling
423  */
424 static int
425 nmdm_modevent(module_t mod, int type, void *data)
426 {
427 	static eventhandler_tag tag;
428 
429         switch(type) {
430         case MOD_LOAD:
431 		tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000);
432 		if (tag == NULL)
433 			return (ENOMEM);
434 		break;
435 
436 	case MOD_SHUTDOWN:
437 		break;
438 
439 	case MOD_UNLOAD:
440 		if (nmdm_count != 0)
441 			return (EBUSY);
442 		EVENTHANDLER_DEREGISTER(dev_clone, tag);
443 		break;
444 
445 	default:
446 		return (EOPNOTSUPP);
447 	}
448 
449 	return (0);
450 }
451 
452 DEV_MODULE(nmdm, nmdm_modevent, NULL);
453