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