1098ca2bdSWarner Losh /*- 2737a1286SJulian Elischer * Copyright (c) 1982, 1986, 1989, 1993 3737a1286SJulian Elischer * The Regents of the University of California. All rights reserved. 4737a1286SJulian Elischer * 5737a1286SJulian Elischer * Redistribution and use in source and binary forms, with or without 6737a1286SJulian Elischer * modification, are permitted provided that the following conditions 7737a1286SJulian Elischer * are met: 8737a1286SJulian Elischer * 1. Redistributions of source code must retain the above copyright 9737a1286SJulian Elischer * notice, this list of conditions and the following disclaimer. 10737a1286SJulian Elischer * 2. Redistributions in binary form must reproduce the above copyright 11737a1286SJulian Elischer * notice, this list of conditions and the following disclaimer in the 12737a1286SJulian Elischer * documentation and/or other materials provided with the distribution. 13737a1286SJulian Elischer * 4. Neither the name of the University nor the names of its contributors 14737a1286SJulian Elischer * may be used to endorse or promote products derived from this software 15737a1286SJulian Elischer * without specific prior written permission. 16737a1286SJulian Elischer * 17737a1286SJulian Elischer * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18737a1286SJulian Elischer * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19737a1286SJulian Elischer * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20737a1286SJulian Elischer * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21737a1286SJulian Elischer * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22737a1286SJulian Elischer * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23737a1286SJulian Elischer * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24737a1286SJulian Elischer * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25737a1286SJulian Elischer * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26737a1286SJulian Elischer * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27737a1286SJulian Elischer * SUCH DAMAGE. 28737a1286SJulian Elischer * 29737a1286SJulian Elischer */ 30737a1286SJulian Elischer 31aad970f1SDavid E. O'Brien #include <sys/cdefs.h> 32aad970f1SDavid E. O'Brien __FBSDID("$FreeBSD$"); 33aad970f1SDavid E. O'Brien 34737a1286SJulian Elischer /* 359308b700SJulian Elischer * Pseudo-nulmodem driver 369308b700SJulian Elischer * Mighty handy for use with serial console in Vmware 37737a1286SJulian Elischer */ 389308b700SJulian Elischer 39737a1286SJulian Elischer #include <sys/param.h> 40737a1286SJulian Elischer #include <sys/systm.h> 41acd3428bSRobert Watson #include <sys/priv.h> 42737a1286SJulian Elischer #include <sys/proc.h> 43737a1286SJulian Elischer #include <sys/tty.h> 44737a1286SJulian Elischer #include <sys/conf.h> 45737a1286SJulian Elischer #include <sys/fcntl.h> 46737a1286SJulian Elischer #include <sys/poll.h> 47737a1286SJulian Elischer #include <sys/kernel.h> 48bc093719SEd Schouten #include <sys/limits.h> 49fe12f24bSPoul-Henning Kamp #include <sys/module.h> 50c24097e4SPoul-Henning Kamp #include <sys/serial.h> 51737a1286SJulian Elischer #include <sys/signalvar.h> 52737a1286SJulian Elischer #include <sys/malloc.h> 539c01a318SPoul-Henning Kamp #include <sys/taskqueue.h> 54737a1286SJulian Elischer 55bc093719SEd Schouten MALLOC_DEFINE(M_NMDM, "nullmodem", "nullmodem data structures"); 56737a1286SJulian Elischer 57bc093719SEd Schouten static tsw_inwakeup_t nmdm_outwakeup; 58bc093719SEd Schouten static tsw_outwakeup_t nmdm_inwakeup; 59bc093719SEd Schouten static tsw_param_t nmdm_param; 60bc093719SEd Schouten static tsw_modem_t nmdm_modem; 61017a4322SPoul-Henning Kamp 62bc093719SEd Schouten static struct ttydevsw nmdm_class = { 63bc093719SEd Schouten .tsw_flags = TF_NOPREFIX, 64bc093719SEd Schouten .tsw_inwakeup = nmdm_inwakeup, 65bc093719SEd Schouten .tsw_outwakeup = nmdm_outwakeup, 66bc093719SEd Schouten .tsw_param = nmdm_param, 67bc093719SEd Schouten .tsw_modem = nmdm_modem, 68737a1286SJulian Elischer }; 69737a1286SJulian Elischer 70bc093719SEd Schouten static void nmdm_task_tty(void *, int); 71737a1286SJulian Elischer 72bc093719SEd Schouten struct nmdmsoftc; 73bc093719SEd Schouten 74bc093719SEd Schouten struct nmdmpart { 75bc093719SEd Schouten struct tty *np_tty; 76bc093719SEd Schouten int np_dcd; 77bc093719SEd Schouten struct task np_task; 78bc093719SEd Schouten struct nmdmpart *np_other; 79bc093719SEd Schouten struct nmdmsoftc *np_pair; 80bc093719SEd Schouten struct callout np_callout; 81bc093719SEd Schouten u_long np_quota; 82bc093719SEd Schouten u_long np_accumulator; 83bc093719SEd Schouten int np_rate; 84bc093719SEd Schouten int np_credits; 85017a4322SPoul-Henning Kamp 86017a4322SPoul-Henning Kamp #define QS 8 /* Quota shift */ 87737a1286SJulian Elischer }; 88737a1286SJulian Elischer 89bc093719SEd Schouten struct nmdmsoftc { 90bc093719SEd Schouten struct nmdmpart ns_part1; 91bc093719SEd Schouten struct nmdmpart ns_part2; 92bc093719SEd Schouten struct mtx ns_mtx; 93737a1286SJulian Elischer }; 94737a1286SJulian Elischer 95bc093719SEd Schouten static int nmdm_count = 0; 96bc093719SEd Schouten 97bc093719SEd Schouten static struct nmdmsoftc * 98bc093719SEd Schouten nmdm_alloc(unsigned long unit) 99bc093719SEd Schouten { 100bc093719SEd Schouten struct nmdmsoftc *ns; 101bc093719SEd Schouten struct tty *tp; 102bc093719SEd Schouten 103bc093719SEd Schouten atomic_add_acq_int(&nmdm_count, 1); 104bc093719SEd Schouten 105bc093719SEd Schouten ns = malloc(sizeof(*ns), M_NMDM, M_WAITOK|M_ZERO); 106bc093719SEd Schouten mtx_init(&ns->ns_mtx, "nmdm", NULL, MTX_DEF); 107bc093719SEd Schouten 108bc093719SEd Schouten /* Hook the pairs together. */ 109bc093719SEd Schouten ns->ns_part1.np_pair = ns; 110bc093719SEd Schouten ns->ns_part1.np_other = &ns->ns_part2; 111bc093719SEd Schouten TASK_INIT(&ns->ns_part1.np_task, 0, nmdm_task_tty, &ns->ns_part1); 1127d82f624SEd Schouten callout_init(&ns->ns_part1.np_callout, CALLOUT_MPSAFE); 113bc093719SEd Schouten 114bc093719SEd Schouten ns->ns_part2.np_pair = ns; 115bc093719SEd Schouten ns->ns_part2.np_other = &ns->ns_part1; 116bc093719SEd Schouten TASK_INIT(&ns->ns_part2.np_task, 0, nmdm_task_tty, &ns->ns_part2); 1177d82f624SEd Schouten callout_init(&ns->ns_part2.np_callout, CALLOUT_MPSAFE); 118bc093719SEd Schouten 119bc093719SEd Schouten /* Create device nodes. */ 120c5e30cc0SEd Schouten tp = ns->ns_part1.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part1, 121bc093719SEd Schouten &ns->ns_mtx); 122bc093719SEd Schouten tty_makedev(tp, NULL, "nmdm%luA", unit); 123bc093719SEd Schouten 124c5e30cc0SEd Schouten tp = ns->ns_part2.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part2, 125bc093719SEd Schouten &ns->ns_mtx); 126bc093719SEd Schouten tty_makedev(tp, NULL, "nmdm%luB", unit); 127bc093719SEd Schouten 128bc093719SEd Schouten return (ns); 129bc093719SEd Schouten } 130b0b03348SPoul-Henning Kamp 131b0b03348SPoul-Henning Kamp static void 1326a113b3dSRobert Watson nmdm_clone(void *arg, struct ucred *cred, char *name, int nameen, 1336a113b3dSRobert Watson struct cdev **dev) 134b0b03348SPoul-Henning Kamp { 135bc093719SEd Schouten unsigned long unit; 136bc093719SEd Schouten char *end; 137bc093719SEd Schouten struct nmdmsoftc *ns; 138b0b03348SPoul-Henning Kamp 139f3732fd1SPoul-Henning Kamp if (*dev != NULL) 140b0b03348SPoul-Henning Kamp return; 141bc093719SEd Schouten if (strncmp(name, "nmdm", 4) != 0) 142b0b03348SPoul-Henning Kamp return; 143bc093719SEd Schouten 144bc093719SEd Schouten /* Device name must be "nmdm%lu%c", where %c is 'A' or 'B'. */ 145bc093719SEd Schouten name += 4; 146bc093719SEd Schouten unit = strtoul(name, &end, 10); 147bc093719SEd Schouten if (unit == ULONG_MAX || name == end) 148b0b03348SPoul-Henning Kamp return; 149bc093719SEd Schouten if ((end[0] != 'A' && end[0] != 'B') || end[1] != '\0') 150b0b03348SPoul-Henning Kamp return; 151bc093719SEd Schouten 152bc093719SEd Schouten /* XXX: pass privileges? */ 153bc093719SEd Schouten ns = nmdm_alloc(unit); 154bc093719SEd Schouten 155bc093719SEd Schouten if (end[0] == 'A') 156bc093719SEd Schouten *dev = ns->ns_part1.np_tty->t_dev; 157b0b03348SPoul-Henning Kamp else 158bc093719SEd Schouten *dev = ns->ns_part2.np_tty->t_dev; 159b0b03348SPoul-Henning Kamp } 160b0b03348SPoul-Henning Kamp 161737a1286SJulian Elischer static void 162017a4322SPoul-Henning Kamp nmdm_timeout(void *arg) 163017a4322SPoul-Henning Kamp { 164bc093719SEd Schouten struct nmdmpart *np = arg; 165017a4322SPoul-Henning Kamp 166bc093719SEd Schouten if (np->np_rate == 0) 167017a4322SPoul-Henning Kamp return; 168017a4322SPoul-Henning Kamp 169017a4322SPoul-Henning Kamp /* 170017a4322SPoul-Henning Kamp * Do a simple Floyd-Steinberg dither here to avoid FP math. 171017a4322SPoul-Henning Kamp * Wipe out unused quota from last tick. 172017a4322SPoul-Henning Kamp */ 173bc093719SEd Schouten np->np_accumulator += np->np_credits; 174bc093719SEd Schouten np->np_quota = np->np_accumulator >> QS; 175bc093719SEd Schouten np->np_accumulator &= ((1 << QS) - 1); 176017a4322SPoul-Henning Kamp 177bc093719SEd Schouten taskqueue_enqueue(taskqueue_swi, &np->np_task); 178bc093719SEd Schouten callout_reset(&np->np_callout, np->np_rate, nmdm_timeout, np); 179017a4322SPoul-Henning Kamp } 180017a4322SPoul-Henning Kamp 181017a4322SPoul-Henning Kamp static void 1829c01a318SPoul-Henning Kamp nmdm_task_tty(void *arg, int pending __unused) 1839c01a318SPoul-Henning Kamp { 1849c01a318SPoul-Henning Kamp struct tty *tp, *otp; 185bc093719SEd Schouten struct nmdmpart *np = arg; 186bc093719SEd Schouten char c; 187737a1286SJulian Elischer 188bc093719SEd Schouten tp = np->np_tty; 189bc093719SEd Schouten tty_lock(tp); 190bc093719SEd Schouten 191bc093719SEd Schouten otp = np->np_other->np_tty; 1929c01a318SPoul-Henning Kamp KASSERT(otp != NULL, ("NULL otp in nmdmstart")); 1939c01a318SPoul-Henning Kamp KASSERT(otp != tp, ("NULL otp == tp nmdmstart")); 194bc093719SEd Schouten if (np->np_other->np_dcd) { 195bc093719SEd Schouten if (!tty_opened(tp)) { 196bc093719SEd Schouten np->np_other->np_dcd = 0; 197bc093719SEd Schouten ttydisc_modem(otp, 0); 1989c01a318SPoul-Henning Kamp } 1999c01a318SPoul-Henning Kamp } else { 200bc093719SEd Schouten if (tty_opened(tp)) { 201bc093719SEd Schouten np->np_other->np_dcd = 1; 202bc093719SEd Schouten ttydisc_modem(otp, 1); 2039c01a318SPoul-Henning Kamp } 2049c01a318SPoul-Henning Kamp } 205017a4322SPoul-Henning Kamp 206bc093719SEd Schouten while (ttydisc_rint_poll(otp) > 0) { 207bc093719SEd Schouten if (np->np_rate && !np->np_quota) 208bc093719SEd Schouten break; 209bc093719SEd Schouten if (ttydisc_getc(tp, &c, 1) != 1) 210bc093719SEd Schouten break; 211bc093719SEd Schouten np->np_quota--; 212bc093719SEd Schouten ttydisc_rint(otp, c, 0); 2139c01a318SPoul-Henning Kamp } 214737a1286SJulian Elischer 215bc093719SEd Schouten ttydisc_rint_done(otp); 216737a1286SJulian Elischer 217bc093719SEd Schouten tty_unlock(tp); 218737a1286SJulian Elischer } 219737a1286SJulian Elischer 220737a1286SJulian Elischer static int 221017a4322SPoul-Henning Kamp bits_per_char(struct termios *t) 222017a4322SPoul-Henning Kamp { 223017a4322SPoul-Henning Kamp int bits; 224017a4322SPoul-Henning Kamp 225017a4322SPoul-Henning Kamp bits = 1; /* start bit */ 226017a4322SPoul-Henning Kamp switch (t->c_cflag & CSIZE) { 227017a4322SPoul-Henning Kamp case CS5: bits += 5; break; 228017a4322SPoul-Henning Kamp case CS6: bits += 6; break; 229017a4322SPoul-Henning Kamp case CS7: bits += 7; break; 230017a4322SPoul-Henning Kamp case CS8: bits += 8; break; 231017a4322SPoul-Henning Kamp } 232017a4322SPoul-Henning Kamp bits++; /* stop bit */ 233017a4322SPoul-Henning Kamp if (t->c_cflag & PARENB) 234017a4322SPoul-Henning Kamp bits++; 235017a4322SPoul-Henning Kamp if (t->c_cflag & CSTOPB) 236017a4322SPoul-Henning Kamp bits++; 237017a4322SPoul-Henning Kamp return (bits); 238017a4322SPoul-Henning Kamp } 239017a4322SPoul-Henning Kamp 240017a4322SPoul-Henning Kamp static int 241bc093719SEd Schouten nmdm_param(struct tty *tp, struct termios *t) 242017a4322SPoul-Henning Kamp { 243bc093719SEd Schouten struct nmdmpart *np = tty_softc(tp); 244017a4322SPoul-Henning Kamp struct tty *tp2; 245017a4322SPoul-Henning Kamp int bpc, rate, speed, i; 246017a4322SPoul-Henning Kamp 247bc093719SEd Schouten tp2 = np->np_other->np_tty; 248017a4322SPoul-Henning Kamp 249bc093719SEd Schouten if (!((t->c_cflag | tp2->t_termios.c_cflag) & CDSR_OFLOW)) { 250bc093719SEd Schouten np->np_rate = 0; 251bc093719SEd Schouten np->np_other->np_rate = 0; 252017a4322SPoul-Henning Kamp return (0); 253017a4322SPoul-Henning Kamp } 254017a4322SPoul-Henning Kamp 255017a4322SPoul-Henning Kamp /* 256017a4322SPoul-Henning Kamp * DSRFLOW one either side enables rate-simulation for both 257017a4322SPoul-Henning Kamp * directions. 258017a4322SPoul-Henning Kamp * NB: the two directions may run at different rates. 259017a4322SPoul-Henning Kamp */ 260017a4322SPoul-Henning Kamp 261017a4322SPoul-Henning Kamp /* Find the larger of the number of bits transmitted */ 262017a4322SPoul-Henning Kamp bpc = imax(bits_per_char(t), bits_per_char(&tp2->t_termios)); 263017a4322SPoul-Henning Kamp 264017a4322SPoul-Henning Kamp for (i = 0; i < 2; i++) { 265017a4322SPoul-Henning Kamp /* Use the slower of our receive and their transmit rate */ 266bc093719SEd Schouten speed = imin(tp2->t_termios.c_ospeed, t->c_ispeed); 267017a4322SPoul-Henning Kamp if (speed == 0) { 268bc093719SEd Schouten np->np_rate = 0; 269bc093719SEd Schouten np->np_other->np_rate = 0; 270017a4322SPoul-Henning Kamp return (0); 271017a4322SPoul-Henning Kamp } 272017a4322SPoul-Henning Kamp 273017a4322SPoul-Henning Kamp speed <<= QS; /* [bit/sec, scaled] */ 274017a4322SPoul-Henning Kamp speed /= bpc; /* [char/sec, scaled] */ 275017a4322SPoul-Henning Kamp rate = (hz << QS) / speed; /* [hz per callout] */ 276017a4322SPoul-Henning Kamp if (rate == 0) 277017a4322SPoul-Henning Kamp rate = 1; 278017a4322SPoul-Henning Kamp 279017a4322SPoul-Henning Kamp speed *= rate; 280017a4322SPoul-Henning Kamp speed /= hz; /* [(char/sec)/tick, scaled */ 281017a4322SPoul-Henning Kamp 282bc093719SEd Schouten np->np_credits = speed; 283bc093719SEd Schouten np->np_rate = rate; 284bc093719SEd Schouten callout_reset(&np->np_callout, rate, nmdm_timeout, np); 285017a4322SPoul-Henning Kamp 286017a4322SPoul-Henning Kamp /* 287017a4322SPoul-Henning Kamp * swap pointers for second pass so the other end gets 288017a4322SPoul-Henning Kamp * updated as well. 289017a4322SPoul-Henning Kamp */ 290bc093719SEd Schouten np = np->np_other; 291017a4322SPoul-Henning Kamp t = &tp2->t_termios; 292017a4322SPoul-Henning Kamp tp2 = tp; 293017a4322SPoul-Henning Kamp } 294bc093719SEd Schouten 295017a4322SPoul-Henning Kamp return (0); 296017a4322SPoul-Henning Kamp } 297017a4322SPoul-Henning Kamp 298017a4322SPoul-Henning Kamp static int 299bc093719SEd Schouten nmdm_modem(struct tty *tp, int sigon, int sigoff) 300c24097e4SPoul-Henning Kamp { 301bc093719SEd Schouten struct nmdmpart *np = tty_softc(tp); 302bc093719SEd Schouten int i = 0; 303c24097e4SPoul-Henning Kamp 304c24097e4SPoul-Henning Kamp if (sigon || sigoff) { 305017a4322SPoul-Henning Kamp if (sigon & SER_DTR) 306bc093719SEd Schouten np->np_other->np_dcd = 1; 307017a4322SPoul-Henning Kamp if (sigoff & SER_DTR) 308bc093719SEd Schouten np->np_other->np_dcd = 0; 309bc093719SEd Schouten 310bc093719SEd Schouten ttydisc_modem(np->np_other->np_tty, np->np_other->np_dcd); 311bc093719SEd Schouten 312c24097e4SPoul-Henning Kamp return (0); 313c24097e4SPoul-Henning Kamp } else { 314bc093719SEd Schouten if (np->np_dcd) 315c24097e4SPoul-Henning Kamp i |= SER_DCD; 316bc093719SEd Schouten if (np->np_other->np_dcd) 317c24097e4SPoul-Henning Kamp i |= SER_DTR; 318bc093719SEd Schouten 319c24097e4SPoul-Henning Kamp return (i); 320c24097e4SPoul-Henning Kamp } 321c24097e4SPoul-Henning Kamp } 322c24097e4SPoul-Henning Kamp 323bc093719SEd Schouten static void 324bc093719SEd Schouten nmdm_inwakeup(struct tty *tp) 325737a1286SJulian Elischer { 326bc093719SEd Schouten struct nmdmpart *np = tty_softc(tp); 327737a1286SJulian Elischer 328bc093719SEd Schouten /* We can receive again, so wake up the other side. */ 329bc093719SEd Schouten taskqueue_enqueue(taskqueue_swi, &np->np_other->np_task); 330737a1286SJulian Elischer } 331737a1286SJulian Elischer 332737a1286SJulian Elischer static void 333bc093719SEd Schouten nmdm_outwakeup(struct tty *tp) 334737a1286SJulian Elischer { 335bc093719SEd Schouten struct nmdmpart *np = tty_softc(tp); 336737a1286SJulian Elischer 337bc093719SEd Schouten /* We can transmit again, so wake up our side. */ 338bc093719SEd Schouten taskqueue_enqueue(taskqueue_swi, &np->np_task); 339737a1286SJulian Elischer } 340737a1286SJulian Elischer 3419308b700SJulian Elischer /* 3429308b700SJulian Elischer * Module handling 3439308b700SJulian Elischer */ 3449308b700SJulian Elischer static int 3459308b700SJulian Elischer nmdm_modevent(module_t mod, int type, void *data) 346737a1286SJulian Elischer { 347b0b03348SPoul-Henning Kamp static eventhandler_tag tag; 3489308b700SJulian Elischer 3499308b700SJulian Elischer switch(type) { 350b0b03348SPoul-Henning Kamp case MOD_LOAD: 351b0b03348SPoul-Henning Kamp tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000); 352b0b03348SPoul-Henning Kamp if (tag == NULL) 353b0b03348SPoul-Henning Kamp return (ENOMEM); 3549308b700SJulian Elischer break; 3559308b700SJulian Elischer 3569308b700SJulian Elischer case MOD_SHUTDOWN: 3579308b700SJulian Elischer break; 358bc093719SEd Schouten 359bc093719SEd Schouten case MOD_UNLOAD: 360bc093719SEd Schouten if (nmdm_count != 0) 361bc093719SEd Schouten return (EBUSY); 362bc093719SEd Schouten EVENTHANDLER_DEREGISTER(dev_clone, tag); 363bc093719SEd Schouten break; 364bc093719SEd Schouten 3659308b700SJulian Elischer default: 366bc093719SEd Schouten return (EOPNOTSUPP); 3679308b700SJulian Elischer } 368bc093719SEd Schouten 369bc093719SEd Schouten return (0); 370737a1286SJulian Elischer } 371737a1286SJulian Elischer 3729308b700SJulian Elischer DEV_MODULE(nmdm, nmdm_modevent, NULL); 373