nmdm.c revision 129939
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: head/sys/dev/nmdm/nmdm.c 129939 2004-06-01 11:57:15Z phk $"); 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#if defined(COMPAT_43) || defined(COMPAT_SUNOS) 45#include <sys/ioctl_compat.h> 46#endif 47#include <sys/proc.h> 48#include <sys/tty.h> 49#include <sys/conf.h> 50#include <sys/fcntl.h> 51#include <sys/poll.h> 52#include <sys/kernel.h> 53#include <sys/module.h> 54#include <sys/vnode.h> 55#include <sys/signalvar.h> 56#include <sys/malloc.h> 57 58MALLOC_DEFINE(M_NLMDM, "nullmodem", "nullmodem data structures"); 59 60static void nmdmstart(struct tty *tp); 61static void nmdmstop(struct tty *tp, int rw); 62static void wakeup_other(struct tty *tp, int flag); 63static void nmdminit(dev_t dev); 64 65static d_open_t nmdmopen; 66static d_close_t nmdmclose; 67static d_read_t nmdmread; 68static d_write_t nmdmwrite; 69static d_ioctl_t nmdmioctl; 70 71static struct cdevsw nmdm_cdevsw = { 72 .d_version = D_VERSION, 73 .d_open = nmdmopen, 74 .d_close = nmdmclose, 75 .d_read = nmdmread, 76 .d_write = nmdmwrite, 77 .d_ioctl = nmdmioctl, 78 .d_name = "nmdn", 79 .d_flags = D_TTY | D_PSEUDO | D_NEEDGIANT, 80}; 81 82#define BUFSIZ 100 /* Chunk size iomoved to/from user */ 83#define NMDM_MAX_NUM 128 /* Artificially limit # devices. */ 84#define PF_STOPPED 0x10 /* user told stopped */ 85#define BFLAG CLONE_FLAG0 86 87struct softpart { 88 struct tty nm_tty; 89 dev_t dev; 90 int modemsignals; /* bits defined in sys/ttycom.h */ 91 int gotbreak; 92}; 93 94struct 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 101static struct clonedevs *nmdmclones; 102static TAILQ_HEAD(,nm_softc) nmdmhead = TAILQ_HEAD_INITIALIZER(nmdmhead); 103 104static void 105nmdm_clone(void *arg, char *name, int nameen, dev_t *dev) 106{ 107 int i, unit; 108 char *p; 109 dev_t d1, d2; 110 111 if (*dev != NODEV) 112 return; 113 if (strcmp(name, "nmdm") == 0) { 114 p = NULL; 115 unit = -1; 116 } else { 117 i = dev_stdclone(name, &p, "nmdm", &unit); 118 if (i == 0) 119 return; 120 if (p[0] != '\0' && p[0] != 'A' && p[0] != 'B') 121 return; 122 else if (p[0] != '\0' && p[1] != '\0') 123 return; 124 } 125 i = clone_create(&nmdmclones, &nmdm_cdevsw, &unit, &d1, 0); 126 if (i) { 127 d1 = make_dev(&nmdm_cdevsw, unit2minor(unit), 128 0, 0, 0666, "nmdm%dA", unit); 129 if (d1 == NULL) 130 return; 131 d2 = make_dev(&nmdm_cdevsw, unit2minor(unit) | BFLAG, 132 0, 0, 0666, "nmdm%dB", unit); 133 if (d2 == NULL) { 134 destroy_dev(d1); 135 return; 136 } 137 d2->si_drv2 = d1; 138 d1->si_drv2 = d2; 139 dev_depends(d1, d2); 140 dev_depends(d2, d1); 141 d1->si_flags |= SI_CHEAPCLONE; 142 d2->si_flags |= SI_CHEAPCLONE; 143 } 144 if (p != NULL && p[0] == 'B') 145 *dev = d1->si_drv2; 146 else 147 *dev = d1; 148} 149 150static void 151nmdm_crossover(struct nm_softc *pti, 152 struct softpart *ourpart, 153 struct softpart *otherpart); 154 155#define GETPARTS(tp, ourpart, otherpart) \ 156do { \ 157 struct nm_softc *pti = tp->t_dev->si_drv1; \ 158 if (tp == &pti->part1.nm_tty) { \ 159 ourpart = &pti->part1; \ 160 otherpart = &pti->part2; \ 161 } else { \ 162 ourpart = &pti->part2; \ 163 otherpart = &pti->part1; \ 164 } \ 165} while (0) 166 167/* 168 * This function creates and initializes a pair of ttys. 169 */ 170static void 171nmdminit(dev_t dev1) 172{ 173 dev_t dev2; 174 struct nm_softc *pt; 175 176 dev2 = dev1->si_drv2; 177 178 dev1->si_flags &= ~SI_CHEAPCLONE; 179 dev2->si_flags &= ~SI_CHEAPCLONE; 180 181 pt = malloc(sizeof(*pt), M_NLMDM, M_WAITOK | M_ZERO); 182 TAILQ_INSERT_TAIL(&nmdmhead, pt, pt_list); 183 dev1->si_drv1 = dev2->si_drv1 = pt; 184 185 pt->part1.dev = dev1; 186 pt->part2.dev = dev2; 187 dev1->si_tty = &pt->part1.nm_tty; 188 dev2->si_tty = &pt->part2.nm_tty; 189 ttyregister(&pt->part1.nm_tty); 190 ttyregister(&pt->part2.nm_tty); 191 pt->part1.nm_tty.t_oproc = nmdmstart; 192 pt->part2.nm_tty.t_oproc = nmdmstart; 193 pt->part1.nm_tty.t_stop = nmdmstop; 194 pt->part2.nm_tty.t_stop = nmdmstop; 195 pt->part2.nm_tty.t_dev = dev1; 196 pt->part1.nm_tty.t_dev = dev2; 197} 198 199/* 200 * Device opened from userland 201 */ 202static int 203nmdmopen(dev_t dev, int flag, int devtype, struct thread *td) 204{ 205 register struct tty *tp, *tp2; 206 int error; 207 struct nm_softc *pti; 208 struct softpart *ourpart, *otherpart; 209 210 if (dev->si_drv1 == NULL) 211 nmdminit(dev); 212 pti = dev->si_drv1; 213 214 if (minor(dev) & BFLAG) 215 tp = &pti->part2.nm_tty; 216 else 217 tp = &pti->part1.nm_tty; 218 GETPARTS(tp, ourpart, otherpart); 219 220 tp2 = &otherpart->nm_tty; 221 ourpart->modemsignals |= TIOCM_LE; 222 223 if ((tp->t_state & TS_ISOPEN) == 0) { 224 ttychars(tp); /* Set up default chars */ 225 tp->t_iflag = TTYDEF_IFLAG; 226 tp->t_oflag = TTYDEF_OFLAG; 227 tp->t_lflag = TTYDEF_LFLAG; 228 tp->t_cflag = TTYDEF_CFLAG; 229 tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED; 230 } else if (tp->t_state & TS_XCLUDE && suser(td)) { 231 return (EBUSY); 232 } else if (pti->pt_prison != td->td_ucred->cr_prison) { 233 return (EBUSY); 234 } 235 236 /* 237 * If the other side is open we have carrier 238 */ 239 if (tp2->t_state & TS_ISOPEN) { 240 (void)(*linesw[tp->t_line].l_modem)(tp, 1); 241 } 242 243 /* 244 * And the other side gets carrier as we are now open. 245 */ 246 (void)(*linesw[tp2->t_line].l_modem)(tp2, 1); 247 248 /* External processing makes no sense here */ 249 tp->t_lflag &= ~EXTPROC; 250 251 /* 252 * Wait here if we don't have carrier. 253 */ 254#if 0 255 while ((tp->t_state & TS_CARR_ON) == 0) { 256 if (flag & FNONBLOCK) 257 break; 258 error = ttysleep(tp, TSA_CARR_ON(tp), TTIPRI | PCATCH, 259 "nmdopn", 0); 260 if (error) 261 return (error); 262 } 263#endif 264 265 /* 266 * Give the line disciplin a chance to set this end up. 267 */ 268 error = (*linesw[tp->t_line].l_open)(dev, tp); 269 270 /* 271 * Wake up the other side. 272 * Theoretically not needed. 273 */ 274 ourpart->modemsignals |= TIOCM_DTR; 275 nmdm_crossover(pti, ourpart, otherpart); 276 if (error == 0) 277 wakeup_other(tp, FREAD|FWRITE); /* XXX */ 278 return (error); 279} 280 281/* 282 * Device closed again 283 */ 284static int 285nmdmclose(dev_t dev, int flag, int mode, struct thread *td) 286{ 287 register struct tty *tp, *tp2; 288 int err; 289 struct softpart *ourpart, *otherpart; 290 291 /* 292 * let the other end know that the game is up 293 */ 294 tp = dev->si_tty; 295 GETPARTS(tp, ourpart, otherpart); 296 tp2 = &otherpart->nm_tty; 297 (void)(*linesw[tp2->t_line].l_modem)(tp2, 0); 298 299 /* 300 * XXX MDMBUF makes no sense for nmdms but would inhibit the above 301 * l_modem(). CLOCAL makes sense but isn't supported. Special 302 * l_modem()s that ignore carrier drop make no sense for nmdms but 303 * may be in use because other parts of the line discipline make 304 * sense for nmdms. Recover by doing everything that a normal 305 * ttymodem() would have done except for sending a SIGHUP. 306 */ 307 if (tp2->t_state & TS_ISOPEN) { 308 tp2->t_state &= ~(TS_CARR_ON | TS_CONNECTED); 309 tp2->t_state |= TS_ZOMBIE; 310 ttyflush(tp2, FREAD | FWRITE); 311 } 312 313 err = (*linesw[tp->t_line].l_close)(tp, flag); 314 ourpart->modemsignals &= ~TIOCM_DTR; 315 nmdm_crossover(dev->si_drv1, ourpart, otherpart); 316 (void) ttyclose(tp); 317 return (err); 318} 319 320/* 321 * handle read(2) request from userland 322 */ 323static int 324nmdmread(dev_t dev, struct uio *uio, int flag) 325{ 326 int error = 0; 327 struct tty *tp, *tp2; 328 struct softpart *ourpart, *otherpart; 329 330 tp = dev->si_tty; 331 GETPARTS(tp, ourpart, otherpart); 332 tp2 = &otherpart->nm_tty; 333 334#if 0 335 if (tp2->t_state & TS_ISOPEN) { 336 error = (*linesw[tp->t_line].l_read)(tp, uio, flag); 337 wakeup_other(tp, FWRITE); 338 } else { 339 if (flag & IO_NDELAY) { 340 return (EWOULDBLOCK); 341 } 342 error = tsleep(TSA_PTC_READ(tp), 343 TTIPRI | PCATCH, "nmdout", 0); 344 } 345 } 346#else 347 if ((error = (*linesw[tp->t_line].l_read)(tp, uio, flag)) == 0) 348 wakeup_other(tp, FWRITE); 349#endif 350 return (error); 351} 352 353/* 354 * Write to pseudo-tty. 355 * Wakeups of controlling tty will happen 356 * indirectly, when tty driver calls nmdmstart. 357 */ 358static int 359nmdmwrite(dev_t dev, struct uio *uio, int flag) 360{ 361 register u_char *cp = 0; 362 register int cc = 0; 363 u_char locbuf[BUFSIZ]; 364 int cnt = 0; 365 int error = 0; 366 struct tty *tp1, *tp; 367 struct softpart *ourpart, *otherpart; 368 369 tp1 = dev->si_tty; 370 /* 371 * Get the other tty struct. 372 * basically we are writing into the INPUT side of the other device. 373 */ 374 GETPARTS(tp1, ourpart, otherpart); 375 tp = &otherpart->nm_tty; 376 377again: 378 if ((tp->t_state & TS_ISOPEN) == 0) 379 return (EIO); 380 while (uio->uio_resid > 0 || cc > 0) { 381 /* 382 * Fill up the buffer if it's empty 383 */ 384 if (cc == 0) { 385 cc = min(uio->uio_resid, BUFSIZ); 386 cp = locbuf; 387 error = uiomove((caddr_t)cp, cc, uio); 388 if (error) 389 return (error); 390 /* check again for safety */ 391 if ((tp->t_state & TS_ISOPEN) == 0) { 392 /* adjust for data copied in but not written */ 393 uio->uio_resid += cc; 394 return (EIO); 395 } 396 } 397 while (cc > 0) { 398 if (((tp->t_rawq.c_cc + tp->t_canq.c_cc) >= (TTYHOG-2)) 399 && ((tp->t_canq.c_cc > 0) || !(tp->t_iflag&ICANON))) { 400 /* 401 * Come here to wait for space in outq, 402 * or space in rawq, or an empty canq. 403 */ 404 wakeup(TSA_HUP_OR_INPUT(tp)); 405 if ((tp->t_state & TS_CONNECTED) == 0) { 406 /* 407 * Data piled up because not connected. 408 * Adjust for data copied in but 409 * not written. 410 */ 411 uio->uio_resid += cc; 412 return (EIO); 413 } 414 if (flag & IO_NDELAY) { 415 /* 416 * Don't wait if asked not to. 417 * Adjust for data copied in but 418 * not written. 419 */ 420 uio->uio_resid += cc; 421 if (cnt == 0) 422 return (EWOULDBLOCK); 423 return (0); 424 } 425 error = tsleep(TSA_PTC_WRITE(tp), 426 TTOPRI | PCATCH, "nmdout", 0); 427 if (error) { 428 /* 429 * Tsleep returned (signal?). 430 * Go find out what the user wants. 431 * adjust for data copied in but 432 * not written 433 */ 434 uio->uio_resid += cc; 435 return (error); 436 } 437 goto again; 438 } 439 (*linesw[tp->t_line].l_rint)(*cp++, tp); 440 cnt++; 441 cc--; 442 } 443 cc = 0; 444 } 445 return (0); 446} 447 448/* 449 * Start output on pseudo-tty. 450 * Wake up process selecting or sleeping for input from controlling tty. 451 */ 452static void 453nmdmstart(struct tty *tp) 454{ 455 register struct nm_softc *pti = tp->t_dev->si_drv1; 456 457 if (tp->t_state & TS_TTSTOP) 458 return; 459 pti->pt_flags &= ~PF_STOPPED; 460 wakeup_other(tp, FREAD); 461} 462 463/* Wakes up the OTHER tty;*/ 464static void 465wakeup_other(struct tty *tp, int flag) 466{ 467 struct softpart *ourpart, *otherpart; 468 469 GETPARTS(tp, ourpart, otherpart); 470 if (flag & FREAD) { 471 selwakeuppri(&otherpart->nm_tty.t_rsel, TTIPRI); 472 wakeup(TSA_PTC_READ((&otherpart->nm_tty))); 473 } 474 if (flag & FWRITE) { 475 selwakeuppri(&otherpart->nm_tty.t_wsel, TTOPRI); 476 wakeup(TSA_PTC_WRITE((&otherpart->nm_tty))); 477 } 478} 479 480/* 481 * stopped output on tty, called when device is closed 482 */ 483static void 484nmdmstop(register struct tty *tp, int flush) 485{ 486 struct nm_softc *pti = tp->t_dev->si_drv1; 487 int flag; 488 489 /* note: FLUSHREAD and FLUSHWRITE already ok */ 490 if (flush == 0) { 491 flush = TIOCPKT_STOP; 492 pti->pt_flags |= PF_STOPPED; 493 } else 494 pti->pt_flags &= ~PF_STOPPED; 495 /* change of perspective */ 496 flag = 0; 497 if (flush & FREAD) 498 flag |= FWRITE; 499 if (flush & FWRITE) 500 flag |= FREAD; 501 wakeup_other(tp, flag); 502} 503 504/* 505 * handle ioctl(2) request from userland 506 */ 507static int 508nmdmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td) 509{ 510 register struct tty *tp = dev->si_tty; 511 struct nm_softc *pti = dev->si_drv1; 512 int error, s; 513 register struct tty *tp2; 514 struct softpart *ourpart, *otherpart; 515 516 s = spltty(); 517 GETPARTS(tp, ourpart, otherpart); 518 tp2 = &otherpart->nm_tty; 519 520 error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, td); 521 if (error == ENOIOCTL) 522 error = ttioctl(tp, cmd, data, flag); 523 if (error == ENOIOCTL) { 524 switch (cmd) { 525 case TIOCSBRK: 526 otherpart->gotbreak = 1; 527 break; 528 case TIOCCBRK: 529 break; 530 case TIOCSDTR: 531 ourpart->modemsignals |= TIOCM_DTR; 532 break; 533 case TIOCCDTR: 534 ourpart->modemsignals &= TIOCM_DTR; 535 break; 536 case TIOCMSET: 537 ourpart->modemsignals = *(int *)data; 538 otherpart->modemsignals = *(int *)data; 539 break; 540 case TIOCMBIS: 541 ourpart->modemsignals |= *(int *)data; 542 break; 543 case TIOCMBIC: 544 ourpart->modemsignals &= ~(*(int *)data); 545 otherpart->modemsignals &= ~(*(int *)data); 546 break; 547 case TIOCMGET: 548 *(int *)data = ourpart->modemsignals; 549 break; 550 case TIOCMSDTRWAIT: 551 break; 552 case TIOCMGDTRWAIT: 553 *(int *)data = 0; 554 break; 555 case TIOCTIMESTAMP: 556 /* FALLTHROUGH */ 557 case TIOCDCDTIMESTAMP: 558 default: 559 splx(s); 560 error = ENOTTY; 561 return (error); 562 } 563 error = 0; 564 nmdm_crossover(pti, ourpart, otherpart); 565 } 566 splx(s); 567 return (error); 568} 569 570static void 571nmdm_crossover(struct nm_softc *pti, struct softpart *ourpart, 572 struct softpart *otherpart) 573{ 574 otherpart->modemsignals &= ~(TIOCM_CTS|TIOCM_CAR); 575 if (ourpart->modemsignals & TIOCM_RTS) 576 otherpart->modemsignals |= TIOCM_CTS; 577 if (ourpart->modemsignals & TIOCM_DTR) 578 otherpart->modemsignals |= TIOCM_CAR; 579} 580 581/* 582 * Module handling 583 */ 584static int 585nmdm_modevent(module_t mod, int type, void *data) 586{ 587 static eventhandler_tag tag; 588 struct nm_softc *pt, *tpt; 589 int error = 0; 590 591 switch(type) { 592 case MOD_LOAD: 593 clone_setup(&nmdmclones); 594 tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000); 595 if (tag == NULL) 596 return (ENOMEM); 597 break; 598 599 case MOD_SHUTDOWN: 600 /* FALLTHROUGH */ 601 case MOD_UNLOAD: 602 EVENTHANDLER_DEREGISTER(dev_clone, tag); 603 TAILQ_FOREACH_SAFE(pt, &nmdmhead, pt_list, tpt) { 604 destroy_dev(pt->part1.dev); 605 TAILQ_REMOVE(&nmdmhead, pt, pt_list); 606 free(pt, M_NLMDM); 607 } 608 clone_cleanup(&nmdmclones); 609 break; 610 default: 611 error = EOPNOTSUPP; 612 } 613 return (error); 614} 615 616DEV_MODULE(nmdm, nmdm_modevent, NULL); 617