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 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 unsigned long unit; 161 char *end; 162 int error; 163 164 if (*dev != NULL) 165 return; 166 if (strncmp(name, "nmdm", 4) != 0) 167 return; 168 169 /* Device name must be "nmdm%lu%c", where %c is 'A' or 'B'. */ 170 name += 4; 171 unit = strtoul(name, &end, 10); 172 if (unit == ULONG_MAX || name == end) 173 return; 174 if ((end[0] != 'A' && end[0] != 'B') || end[1] != '\0') 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 error = tty_makedevf(tp, NULL, end[0] == 'A' ? TTYMK_CLONING : 0, 195 "nmdm%luA", unit); 196 if (error) { 197 mtx_destroy(&ns->ns_mtx); 198 free(ns, M_NMDM); 199 return; 200 } 201 202 tp = ns->ns_part2.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part2, 203 &ns->ns_mtx); 204 error = tty_makedevf(tp, NULL, end[0] == 'B' ? TTYMK_CLONING : 0, 205 "nmdm%luB", unit); 206 if (error) { 207 mtx_lock(&ns->ns_mtx); 208 /* see nmdm_free() */ 209 ns->ns_part1.np_other = NULL; 210 atomic_add_int(&nmdm_count, 1); 211 tty_rel_gone(ns->ns_part1.np_tty); 212 return; 213 } 214 215 if (end[0] == 'A') 216 *dev = ns->ns_part1.np_tty->t_dev; 217 else 218 *dev = ns->ns_part2.np_tty->t_dev; 219 220 atomic_add_int(&nmdm_count, 1); 221 } 222 223 static void 224 nmdm_timeout(void *arg) 225 { 226 struct nmdmpart *np = arg; 227 228 if (np->np_rate == 0) 229 return; 230 231 /* 232 * Do a simple Floyd-Steinberg dither here to avoid FP math. 233 * Wipe out unused quota from last tick. 234 */ 235 np->np_accumulator += np->np_credits; 236 np->np_quota = np->np_accumulator >> QS; 237 np->np_accumulator &= ((1 << QS) - 1); 238 239 taskqueue_enqueue(taskqueue_swi, &np->np_task); 240 callout_reset(&np->np_callout, np->np_rate, nmdm_timeout, np); 241 } 242 243 static void 244 nmdm_task_tty(void *arg, int pending __unused) 245 { 246 struct tty *tp, *otp; 247 struct nmdmpart *np = arg; 248 char c; 249 250 tp = np->np_tty; 251 tty_lock(tp); 252 if (tty_gone(tp)) { 253 tty_unlock(tp); 254 return; 255 } 256 257 otp = np->np_other->np_tty; 258 KASSERT(otp != NULL, ("NULL otp in nmdmstart")); 259 KASSERT(otp != tp, ("NULL otp == tp nmdmstart")); 260 if (np->np_other->np_dcd) { 261 if (!tty_opened(tp)) { 262 np->np_other->np_dcd = 0; 263 ttydisc_modem(otp, 0); 264 } 265 } else { 266 if (tty_opened(tp)) { 267 np->np_other->np_dcd = 1; 268 ttydisc_modem(otp, 1); 269 } 270 } 271 272 /* This may happen when we are in detach process. */ 273 if (tty_gone(otp)) { 274 tty_unlock(otp); 275 return; 276 } 277 278 while (ttydisc_rint_poll(otp) > 0) { 279 if (np->np_rate && !np->np_quota) 280 break; 281 if (ttydisc_getc(tp, &c, 1) != 1) 282 break; 283 np->np_quota--; 284 ttydisc_rint(otp, c, 0); 285 } 286 287 ttydisc_rint_done(otp); 288 289 tty_unlock(tp); 290 } 291 292 static int 293 bits_per_char(struct termios *t) 294 { 295 int bits; 296 297 bits = 1; /* start bit */ 298 switch (t->c_cflag & CSIZE) { 299 case CS5: bits += 5; break; 300 case CS6: bits += 6; break; 301 case CS7: bits += 7; break; 302 case CS8: bits += 8; break; 303 } 304 bits++; /* stop bit */ 305 if (t->c_cflag & PARENB) 306 bits++; 307 if (t->c_cflag & CSTOPB) 308 bits++; 309 return (bits); 310 } 311 312 static int 313 nmdm_param(struct tty *tp, struct termios *t) 314 { 315 struct nmdmpart *np = tty_softc(tp); 316 struct tty *tp2; 317 int bpc, rate, speed, i; 318 319 tp2 = np->np_other->np_tty; 320 321 if (!((t->c_cflag | tp2->t_termios.c_cflag) & CDSR_OFLOW)) { 322 np->np_rate = 0; 323 np->np_other->np_rate = 0; 324 return (0); 325 } 326 327 /* 328 * DSRFLOW one either side enables rate-simulation for both 329 * directions. 330 * NB: the two directions may run at different rates. 331 */ 332 333 /* Find the larger of the number of bits transmitted */ 334 bpc = imax(bits_per_char(t), bits_per_char(&tp2->t_termios)); 335 336 for (i = 0; i < 2; i++) { 337 /* Use the slower of our receive and their transmit rate */ 338 speed = imin(tp2->t_termios.c_ospeed, t->c_ispeed); 339 if (speed == 0) { 340 np->np_rate = 0; 341 np->np_other->np_rate = 0; 342 return (0); 343 } 344 345 speed <<= QS; /* [bit/sec, scaled] */ 346 speed /= bpc; /* [char/sec, scaled] */ 347 rate = (hz << QS) / speed; /* [hz per callout] */ 348 if (rate == 0) 349 rate = 1; 350 351 speed *= rate; 352 speed /= hz; /* [(char/sec)/tick, scaled */ 353 354 np->np_credits = speed; 355 np->np_rate = rate; 356 callout_reset(&np->np_callout, rate, nmdm_timeout, np); 357 358 /* 359 * swap pointers for second pass so the other end gets 360 * updated as well. 361 */ 362 np = np->np_other; 363 t = &tp2->t_termios; 364 tp2 = tp; 365 } 366 367 return (0); 368 } 369 370 static int 371 nmdm_modem(struct tty *tp, int sigon, int sigoff) 372 { 373 struct nmdmpart *np = tty_softc(tp); 374 int i = 0; 375 376 if (sigon || sigoff) { 377 if (sigon & SER_DTR) 378 np->np_other->np_dcd = 1; 379 if (sigoff & SER_DTR) 380 np->np_other->np_dcd = 0; 381 382 ttydisc_modem(np->np_other->np_tty, np->np_other->np_dcd); 383 384 return (0); 385 } else { 386 if (np->np_dcd) 387 i |= SER_DCD; 388 if (np->np_other->np_dcd) 389 i |= SER_DTR; 390 391 return (i); 392 } 393 } 394 395 static void 396 nmdm_inwakeup(struct tty *tp) 397 { 398 struct nmdmpart *np = tty_softc(tp); 399 400 /* We can receive again, so wake up the other side. */ 401 taskqueue_enqueue(taskqueue_swi, &np->np_other->np_task); 402 } 403 404 static void 405 nmdm_outwakeup(struct tty *tp) 406 { 407 struct nmdmpart *np = tty_softc(tp); 408 409 /* We can transmit again, so wake up our side. */ 410 taskqueue_enqueue(taskqueue_swi, &np->np_task); 411 } 412 413 /* 414 * Module handling 415 */ 416 static int 417 nmdm_modevent(module_t mod, int type, void *data) 418 { 419 static eventhandler_tag tag; 420 421 switch(type) { 422 case MOD_LOAD: 423 tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000); 424 if (tag == NULL) 425 return (ENOMEM); 426 break; 427 428 case MOD_SHUTDOWN: 429 break; 430 431 case MOD_UNLOAD: 432 if (nmdm_count != 0) 433 return (EBUSY); 434 EVENTHANDLER_DEREGISTER(dev_clone, tag); 435 break; 436 437 default: 438 return (EOPNOTSUPP); 439 } 440 441 return (0); 442 } 443 444 DEV_MODULE(nmdm, nmdm_modevent, NULL); 445