nmdm.c revision 129879
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 129879 2004-05-30 20:08:47Z 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 nmdmstop(tp, FREAD|FWRITE); 317 (void) ttyclose(tp); 318 return (err); 319} 320 321/* 322 * handle read(2) request from userland 323 */ 324static int 325nmdmread(dev_t dev, struct uio *uio, int flag) 326{ 327 int error = 0; 328 struct tty *tp, *tp2; 329 struct softpart *ourpart, *otherpart; 330 331 tp = dev->si_tty; 332 GETPARTS(tp, ourpart, otherpart); 333 tp2 = &otherpart->nm_tty; 334 335#if 0 336 if (tp2->t_state & TS_ISOPEN) { 337 error = (*linesw[tp->t_line].l_read)(tp, uio, flag); 338 wakeup_other(tp, FWRITE); 339 } else { 340 if (flag & IO_NDELAY) { 341 return (EWOULDBLOCK); 342 } 343 error = tsleep(TSA_PTC_READ(tp), 344 TTIPRI | PCATCH, "nmdout", 0); 345 } 346 } 347#else 348 if ((error = (*linesw[tp->t_line].l_read)(tp, uio, flag)) == 0) 349 wakeup_other(tp, FWRITE); 350#endif 351 return (error); 352} 353 354/* 355 * Write to pseudo-tty. 356 * Wakeups of controlling tty will happen 357 * indirectly, when tty driver calls nmdmstart. 358 */ 359static int 360nmdmwrite(dev_t dev, struct uio *uio, int flag) 361{ 362 register u_char *cp = 0; 363 register int cc = 0; 364 u_char locbuf[BUFSIZ]; 365 int cnt = 0; 366 int error = 0; 367 struct tty *tp1, *tp; 368 struct softpart *ourpart, *otherpart; 369 370 tp1 = dev->si_tty; 371 /* 372 * Get the other tty struct. 373 * basically we are writing into the INPUT side of the other device. 374 */ 375 GETPARTS(tp1, ourpart, otherpart); 376 tp = &otherpart->nm_tty; 377 378again: 379 if ((tp->t_state & TS_ISOPEN) == 0) 380 return (EIO); 381 while (uio->uio_resid > 0 || cc > 0) { 382 /* 383 * Fill up the buffer if it's empty 384 */ 385 if (cc == 0) { 386 cc = min(uio->uio_resid, BUFSIZ); 387 cp = locbuf; 388 error = uiomove((caddr_t)cp, cc, uio); 389 if (error) 390 return (error); 391 /* check again for safety */ 392 if ((tp->t_state & TS_ISOPEN) == 0) { 393 /* adjust for data copied in but not written */ 394 uio->uio_resid += cc; 395 return (EIO); 396 } 397 } 398 while (cc > 0) { 399 if (((tp->t_rawq.c_cc + tp->t_canq.c_cc) >= (TTYHOG-2)) 400 && ((tp->t_canq.c_cc > 0) || !(tp->t_iflag&ICANON))) { 401 /* 402 * Come here to wait for space in outq, 403 * or space in rawq, or an empty canq. 404 */ 405 wakeup(TSA_HUP_OR_INPUT(tp)); 406 if ((tp->t_state & TS_CONNECTED) == 0) { 407 /* 408 * Data piled up because not connected. 409 * Adjust for data copied in but 410 * not written. 411 */ 412 uio->uio_resid += cc; 413 return (EIO); 414 } 415 if (flag & IO_NDELAY) { 416 /* 417 * Don't wait if asked not to. 418 * Adjust for data copied in but 419 * not written. 420 */ 421 uio->uio_resid += cc; 422 if (cnt == 0) 423 return (EWOULDBLOCK); 424 return (0); 425 } 426 error = tsleep(TSA_PTC_WRITE(tp), 427 TTOPRI | PCATCH, "nmdout", 0); 428 if (error) { 429 /* 430 * Tsleep returned (signal?). 431 * Go find out what the user wants. 432 * adjust for data copied in but 433 * not written 434 */ 435 uio->uio_resid += cc; 436 return (error); 437 } 438 goto again; 439 } 440 (*linesw[tp->t_line].l_rint)(*cp++, tp); 441 cnt++; 442 cc--; 443 } 444 cc = 0; 445 } 446 return (0); 447} 448 449/* 450 * Start output on pseudo-tty. 451 * Wake up process selecting or sleeping for input from controlling tty. 452 */ 453static void 454nmdmstart(struct tty *tp) 455{ 456 register struct nm_softc *pti = tp->t_dev->si_drv1; 457 458 if (tp->t_state & TS_TTSTOP) 459 return; 460 pti->pt_flags &= ~PF_STOPPED; 461 wakeup_other(tp, FREAD); 462} 463 464/* Wakes up the OTHER tty;*/ 465static void 466wakeup_other(struct tty *tp, int flag) 467{ 468 struct softpart *ourpart, *otherpart; 469 470 GETPARTS(tp, ourpart, otherpart); 471 if (flag & FREAD) { 472 selwakeuppri(&otherpart->nm_tty.t_rsel, TTIPRI); 473 wakeup(TSA_PTC_READ((&otherpart->nm_tty))); 474 } 475 if (flag & FWRITE) { 476 selwakeuppri(&otherpart->nm_tty.t_wsel, TTOPRI); 477 wakeup(TSA_PTC_WRITE((&otherpart->nm_tty))); 478 } 479} 480 481/* 482 * stopped output on tty, called when device is closed 483 */ 484static void 485nmdmstop(register struct tty *tp, int flush) 486{ 487 struct nm_softc *pti = tp->t_dev->si_drv1; 488 int flag; 489 490 /* note: FLUSHREAD and FLUSHWRITE already ok */ 491 if (flush == 0) { 492 flush = TIOCPKT_STOP; 493 pti->pt_flags |= PF_STOPPED; 494 } else 495 pti->pt_flags &= ~PF_STOPPED; 496 /* change of perspective */ 497 flag = 0; 498 if (flush & FREAD) 499 flag |= FWRITE; 500 if (flush & FWRITE) 501 flag |= FREAD; 502 wakeup_other(tp, flag); 503} 504 505/* 506 * handle ioctl(2) request from userland 507 */ 508static int 509nmdmioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td) 510{ 511 register struct tty *tp = dev->si_tty; 512 struct nm_softc *pti = dev->si_drv1; 513 int error, s; 514 register struct tty *tp2; 515 struct softpart *ourpart, *otherpart; 516 517 s = spltty(); 518 GETPARTS(tp, ourpart, otherpart); 519 tp2 = &otherpart->nm_tty; 520 521 error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, td); 522 if (error == ENOIOCTL) 523 error = ttioctl(tp, cmd, data, flag); 524 if (error == ENOIOCTL) { 525 switch (cmd) { 526 case TIOCSBRK: 527 otherpart->gotbreak = 1; 528 break; 529 case TIOCCBRK: 530 break; 531 case TIOCSDTR: 532 ourpart->modemsignals |= TIOCM_DTR; 533 break; 534 case TIOCCDTR: 535 ourpart->modemsignals &= TIOCM_DTR; 536 break; 537 case TIOCMSET: 538 ourpart->modemsignals = *(int *)data; 539 otherpart->modemsignals = *(int *)data; 540 break; 541 case TIOCMBIS: 542 ourpart->modemsignals |= *(int *)data; 543 break; 544 case TIOCMBIC: 545 ourpart->modemsignals &= ~(*(int *)data); 546 otherpart->modemsignals &= ~(*(int *)data); 547 break; 548 case TIOCMGET: 549 *(int *)data = ourpart->modemsignals; 550 break; 551 case TIOCMSDTRWAIT: 552 break; 553 case TIOCMGDTRWAIT: 554 *(int *)data = 0; 555 break; 556 case TIOCTIMESTAMP: 557 /* FALLTHROUGH */ 558 case TIOCDCDTIMESTAMP: 559 default: 560 splx(s); 561 error = ENOTTY; 562 return (error); 563 } 564 error = 0; 565 nmdm_crossover(pti, ourpart, otherpart); 566 } 567 splx(s); 568 return (error); 569} 570 571static void 572nmdm_crossover(struct nm_softc *pti, struct softpart *ourpart, 573 struct softpart *otherpart) 574{ 575 otherpart->modemsignals &= ~(TIOCM_CTS|TIOCM_CAR); 576 if (ourpart->modemsignals & TIOCM_RTS) 577 otherpart->modemsignals |= TIOCM_CTS; 578 if (ourpart->modemsignals & TIOCM_DTR) 579 otherpart->modemsignals |= TIOCM_CAR; 580} 581 582/* 583 * Module handling 584 */ 585static int 586nmdm_modevent(module_t mod, int type, void *data) 587{ 588 static eventhandler_tag tag; 589 struct nm_softc *pt, *tpt; 590 int error = 0; 591 592 switch(type) { 593 case MOD_LOAD: 594 clone_setup(&nmdmclones); 595 tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000); 596 if (tag == NULL) 597 return (ENOMEM); 598 break; 599 600 case MOD_SHUTDOWN: 601 /* FALLTHROUGH */ 602 case MOD_UNLOAD: 603 EVENTHANDLER_DEREGISTER(dev_clone, tag); 604 TAILQ_FOREACH_SAFE(pt, &nmdmhead, pt_list, tpt) { 605 destroy_dev(pt->part1.dev); 606 TAILQ_REMOVE(&nmdmhead, pt, pt_list); 607 free(pt, M_NLMDM); 608 } 609 clone_cleanup(&nmdmclones); 610 break; 611 default: 612 error = EOPNOTSUPP; 613 } 614 return (error); 615} 616 617DEV_MODULE(nmdm, nmdm_modevent, NULL); 618