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