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