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 "opt_compat.h" 40 #include "opt_tty.h" 41 42 #include <sys/param.h> 43 #include <sys/systm.h> 44 #include <sys/priv.h> 45 #include <sys/proc.h> 46 #include <sys/tty.h> 47 #include <sys/conf.h> 48 #include <sys/fcntl.h> 49 #include <sys/poll.h> 50 #include <sys/kernel.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 MALLOC_DEFINE(M_NLMDM, "nullmodem", "nullmodem data structures"); 58 59 static d_close_t nmdmclose; 60 static t_modem_t nmdmmodem; 61 static d_open_t nmdmopen; 62 static t_oproc_t nmdmoproc; 63 static t_param_t nmdmparam; 64 static t_stop_t nmdmstop; 65 66 static struct cdevsw nmdm_cdevsw = { 67 .d_version = D_VERSION, 68 .d_open = nmdmopen, 69 .d_close = nmdmclose, 70 .d_name = "nmdn", 71 .d_flags = D_TTY | D_PSEUDO | D_NEEDGIANT, 72 }; 73 74 #define BUFSIZ 100 /* Chunk size iomoved to/from user */ 75 #define NMDM_MAX_NUM 128 /* Artificially limit # devices. */ 76 #define PF_STOPPED 0x10 /* user told stopped */ 77 #define BFLAG CLONE_FLAG0 78 79 struct softpart { 80 struct tty *nm_tty; 81 struct cdev *dev; 82 int nm_dcd; 83 struct task pt_task; 84 struct softpart *other; 85 struct callout co; 86 u_long quota; 87 u_long accumulator; 88 int rate; 89 int credits; 90 91 #define QS 8 /* Quota shift */ 92 }; 93 94 struct nm_softc { 95 TAILQ_ENTRY(nm_softc) pt_list; 96 int pt_flags; 97 struct softpart part1, part2; 98 struct prison *pt_prison; 99 }; 100 101 static struct clonedevs *nmdmclones; 102 static TAILQ_HEAD(,nm_softc) nmdmhead = TAILQ_HEAD_INITIALIZER(nmdmhead); 103 104 static void 105 nmdm_clone(void *arg, struct ucred *cred, char *name, int nameen, 106 struct cdev **dev) 107 { 108 int i, unit; 109 char *p; 110 struct cdev *d1, *d2; 111 112 if (*dev != NULL) 113 return; 114 if (strcmp(name, "nmdm") == 0) { 115 p = NULL; 116 unit = -1; 117 } else { 118 i = dev_stdclone(name, &p, "nmdm", &unit); 119 if (i == 0) 120 return; 121 if (p[0] != '\0' && p[0] != 'A' && p[0] != 'B') 122 return; 123 else if (p[0] != '\0' && p[1] != '\0') 124 return; 125 } 126 i = clone_create(&nmdmclones, &nmdm_cdevsw, &unit, &d1, 0); 127 if (i) { 128 d1 = make_dev(&nmdm_cdevsw, unit2minor(unit), 129 0, 0, 0666, "nmdm%dA", unit); 130 if (d1 == NULL) 131 return; 132 d2 = make_dev(&nmdm_cdevsw, unit2minor(unit) | BFLAG, 133 0, 0, 0666, "nmdm%dB", unit); 134 if (d2 == NULL) { 135 destroy_dev(d1); 136 return; 137 } 138 d2->si_drv2 = d1; 139 d1->si_drv2 = d2; 140 dev_depends(d1, d2); 141 dev_depends(d2, d1); 142 d1->si_flags |= SI_CHEAPCLONE; 143 d2->si_flags |= SI_CHEAPCLONE; 144 } 145 if (p != NULL && p[0] == 'B') 146 *dev = d1->si_drv2; 147 else 148 *dev = d1; 149 dev_ref(*dev); 150 } 151 152 static void 153 nmdm_timeout(void *arg) 154 { 155 struct softpart *sp; 156 157 sp = arg; 158 159 if (sp->rate == 0) 160 return; 161 162 /* 163 * Do a simple Floyd-Steinberg dither here to avoid FP math. 164 * Wipe out unused quota from last tick. 165 */ 166 sp->accumulator += sp->credits; 167 sp->quota = sp->accumulator >> QS; 168 sp->accumulator &= ((1 << QS) - 1); 169 170 taskqueue_enqueue(taskqueue_swi_giant, &sp->pt_task); 171 callout_reset(&sp->co, sp->rate, nmdm_timeout, arg); 172 } 173 174 static void 175 nmdm_task_tty(void *arg, int pending __unused) 176 { 177 struct tty *tp, *otp; 178 struct softpart *sp; 179 int c; 180 181 tp = arg; 182 sp = tp->t_sc; 183 otp = sp->other->nm_tty; 184 KASSERT(otp != NULL, ("NULL otp in nmdmstart")); 185 KASSERT(otp != tp, ("NULL otp == tp nmdmstart")); 186 if (sp->other->nm_dcd) { 187 if (!(tp->t_state & TS_ISOPEN)) { 188 sp->other->nm_dcd = 0; 189 (void)ttyld_modem(otp, 0); 190 } 191 } else { 192 if (tp->t_state & TS_ISOPEN) { 193 sp->other->nm_dcd = 1; 194 (void)ttyld_modem(otp, 1); 195 } 196 } 197 if (tp->t_state & TS_TTSTOP) 198 return; 199 while (tp->t_outq.c_cc != 0) { 200 if (sp->rate && !sp->quota) 201 return; 202 if (otp->t_state & TS_TBLOCK) 203 return; 204 sp->quota--; 205 c = getc(&tp->t_outq); 206 if (otp->t_state & TS_ISOPEN) 207 ttyld_rint(otp, c); 208 } 209 if (tp->t_outq.c_cc == 0) 210 ttwwakeup(tp); 211 212 } 213 214 /* 215 * This function creates and initializes a pair of ttys. 216 */ 217 static void 218 nmdminit(struct cdev *dev1) 219 { 220 struct cdev *dev2; 221 struct nm_softc *pt; 222 223 dev2 = dev1->si_drv2; 224 225 dev1->si_flags &= ~SI_CHEAPCLONE; 226 dev2->si_flags &= ~SI_CHEAPCLONE; 227 228 pt = malloc(sizeof(*pt), M_NLMDM, M_WAITOK | M_ZERO); 229 TAILQ_INSERT_TAIL(&nmdmhead, pt, pt_list); 230 231 dev1->si_drv1 = dev2->si_drv1 = pt; 232 233 pt->part1.dev = dev1; 234 pt->part2.dev = dev2; 235 236 pt->part1.nm_tty = ttyalloc(); 237 pt->part1.nm_tty->t_oproc = nmdmoproc; 238 pt->part1.nm_tty->t_stop = nmdmstop; 239 pt->part1.nm_tty->t_modem = nmdmmodem; 240 pt->part1.nm_tty->t_param = nmdmparam; 241 pt->part1.nm_tty->t_dev = dev1; 242 pt->part1.nm_tty->t_sc = &pt->part1; 243 TASK_INIT(&pt->part1.pt_task, 0, nmdm_task_tty, pt->part1.nm_tty); 244 callout_init(&pt->part1.co, 0); 245 246 pt->part2.nm_tty = ttyalloc(); 247 pt->part2.nm_tty->t_oproc = nmdmoproc; 248 pt->part2.nm_tty->t_stop = nmdmstop; 249 pt->part2.nm_tty->t_modem = nmdmmodem; 250 pt->part2.nm_tty->t_param = nmdmparam; 251 pt->part2.nm_tty->t_dev = dev2; 252 pt->part2.nm_tty->t_sc = &pt->part2; 253 TASK_INIT(&pt->part2.pt_task, 0, nmdm_task_tty, pt->part2.nm_tty); 254 callout_init(&pt->part2.co, 0); 255 256 pt->part1.other = &pt->part2; 257 pt->part2.other = &pt->part1; 258 259 dev1->si_tty = pt->part1.nm_tty; 260 dev1->si_drv1 = pt; 261 262 dev2->si_tty = pt->part2.nm_tty; 263 dev2->si_drv1 = pt; 264 } 265 266 /* 267 * Device opened from userland 268 */ 269 static int 270 nmdmopen(struct cdev *dev, int flag, int devtype, struct thread *td) 271 { 272 struct tty *tp, *tp2; 273 int error; 274 struct nm_softc *pti; 275 struct softpart *sp; 276 277 if (dev->si_drv1 == NULL) 278 nmdminit(dev); 279 pti = dev->si_drv1; 280 if (pti->pt_prison != td->td_ucred->cr_prison) 281 return (EBUSY); 282 283 tp = dev->si_tty; 284 sp = tp->t_sc; 285 tp2 = sp->other->nm_tty; 286 287 if ((tp->t_state & TS_ISOPEN) == 0) { 288 ttyinitmode(tp, 0, 0); 289 ttsetwater(tp); /* XXX ? */ 290 } else if (tp->t_state & TS_XCLUDE && 291 priv_check(td, PRIV_TTY_EXCLUSIVE)) { 292 return (EBUSY); 293 } 294 295 error = ttyld_open(tp, dev); 296 return (error); 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 nmdmparam(struct tty *tp, struct termios *t) 321 { 322 struct softpart *sp; 323 struct tty *tp2; 324 int bpc, rate, speed, i; 325 326 sp = tp->t_sc; 327 tp2 = sp->other->nm_tty; 328 329 if (!((t->c_cflag | tp2->t_cflag) & CDSR_OFLOW)) { 330 sp->rate = 0; 331 sp->other->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_ospeed, t->c_ispeed); 347 if (speed == 0) { 348 sp->rate = 0; 349 sp->other->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 sp->credits = speed; 363 sp->rate = rate; 364 callout_reset(&sp->co, rate, nmdm_timeout, sp); 365 366 /* 367 * swap pointers for second pass so the other end gets 368 * updated as well. 369 */ 370 sp = sp->other; 371 t = &tp2->t_termios; 372 tp2 = tp; 373 } 374 return (0); 375 } 376 377 static int 378 nmdmmodem(struct tty *tp, int sigon, int sigoff) 379 { 380 struct softpart *sp; 381 int i; 382 383 sp = tp->t_sc; 384 if (sigon || sigoff) { 385 if (sigon & SER_DTR) 386 sp->other->nm_dcd = 1; 387 if (sigoff & SER_DTR) 388 sp->other->nm_dcd = 0; 389 ttyld_modem(sp->other->nm_tty, sp->other->nm_dcd); 390 return (0); 391 } else { 392 i = 0; 393 if (sp->nm_dcd) 394 i |= SER_DCD; 395 if (sp->other->nm_dcd) 396 i |= SER_DTR; 397 return (i); 398 } 399 } 400 401 static int 402 nmdmclose(struct cdev *dev, int flag, int mode, struct thread *td) 403 { 404 struct tty *tp = dev->si_tty; 405 int error; 406 407 error = ttyld_close(tp, flag); 408 (void) tty_close(dev->si_tty); 409 410 return (error); 411 } 412 413 static void 414 nmdmoproc(struct tty *tp) 415 { 416 struct softpart *pt; 417 418 pt = tp->t_sc; 419 taskqueue_enqueue(taskqueue_swi_giant, &pt->pt_task); 420 } 421 422 static void 423 nmdmstop(struct tty *tp, int flush) 424 { 425 struct softpart *pt; 426 427 pt = tp->t_sc; 428 taskqueue_enqueue(taskqueue_swi_giant, &pt->pt_task); 429 } 430 431 /* 432 * Module handling 433 */ 434 static int 435 nmdm_modevent(module_t mod, int type, void *data) 436 { 437 static eventhandler_tag tag; 438 struct nm_softc *pt, *tpt; 439 int error = 0; 440 441 switch(type) { 442 case MOD_LOAD: 443 clone_setup(&nmdmclones); 444 tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000); 445 if (tag == NULL) 446 return (ENOMEM); 447 break; 448 449 case MOD_SHUTDOWN: 450 /* FALLTHROUGH */ 451 case MOD_UNLOAD: 452 EVENTHANDLER_DEREGISTER(dev_clone, tag); 453 TAILQ_FOREACH_SAFE(pt, &nmdmhead, pt_list, tpt) { 454 destroy_dev(pt->part1.dev); 455 TAILQ_REMOVE(&nmdmhead, pt, pt_list); 456 free(pt, M_NLMDM); 457 } 458 clone_cleanup(&nmdmclones); 459 break; 460 default: 461 error = EOPNOTSUPP; 462 } 463 return (error); 464 } 465 466 DEV_MODULE(nmdm, nmdm_modevent, NULL); 467