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 * 3. 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 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 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 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 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 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 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 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 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 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 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 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