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