1139749Simp/*- 273161Sjulian * Copyright (c) 1982, 1986, 1989, 1993 373161Sjulian * The Regents of the University of California. All rights reserved. 473161Sjulian * 573161Sjulian * Redistribution and use in source and binary forms, with or without 673161Sjulian * modification, are permitted provided that the following conditions 773161Sjulian * are met: 873161Sjulian * 1. Redistributions of source code must retain the above copyright 973161Sjulian * notice, this list of conditions and the following disclaimer. 1073161Sjulian * 2. Redistributions in binary form must reproduce the above copyright 1173161Sjulian * notice, this list of conditions and the following disclaimer in the 1273161Sjulian * documentation and/or other materials provided with the distribution. 1373161Sjulian * 4. Neither the name of the University nor the names of its contributors 1473161Sjulian * may be used to endorse or promote products derived from this software 1573161Sjulian * without specific prior written permission. 1673161Sjulian * 1773161Sjulian * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 1873161Sjulian * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1973161Sjulian * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2073161Sjulian * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 2173161Sjulian * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2273161Sjulian * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2373161Sjulian * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2473161Sjulian * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2573161Sjulian * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2673161Sjulian * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2773161Sjulian * SUCH DAMAGE. 2873161Sjulian * 2973161Sjulian */ 3073161Sjulian 31119418Sobrien#include <sys/cdefs.h> 32119418Sobrien__FBSDID("$FreeBSD$"); 33119418Sobrien 3473161Sjulian/* 3590995Sjulian * Pseudo-nulmodem driver 3690995Sjulian * Mighty handy for use with serial console in Vmware 3773161Sjulian */ 3890995Sjulian 3973161Sjulian#include <sys/param.h> 4073161Sjulian#include <sys/systm.h> 41164033Srwatson#include <sys/priv.h> 4273161Sjulian#include <sys/proc.h> 4373161Sjulian#include <sys/tty.h> 4473161Sjulian#include <sys/conf.h> 4573161Sjulian#include <sys/fcntl.h> 4673161Sjulian#include <sys/poll.h> 4773161Sjulian#include <sys/kernel.h> 48181905Sed#include <sys/limits.h> 49129879Sphk#include <sys/module.h> 50131579Sphk#include <sys/serial.h> 5173161Sjulian#include <sys/signalvar.h> 5273161Sjulian#include <sys/malloc.h> 53129968Sphk#include <sys/taskqueue.h> 5473161Sjulian 55249132Smavstatic MALLOC_DEFINE(M_NMDM, "nullmodem", "nullmodem data structures"); 5673161Sjulian 57181905Sedstatic tsw_inwakeup_t nmdm_outwakeup; 58181905Sedstatic tsw_outwakeup_t nmdm_inwakeup; 59181905Sedstatic tsw_param_t nmdm_param; 60181905Sedstatic tsw_modem_t nmdm_modem; 6173161Sjulian 62181905Sedstatic struct ttydevsw nmdm_class = { 63181905Sed .tsw_flags = TF_NOPREFIX, 64181905Sed .tsw_inwakeup = nmdm_inwakeup, 65181905Sed .tsw_outwakeup = nmdm_outwakeup, 66181905Sed .tsw_param = nmdm_param, 67181905Sed .tsw_modem = nmdm_modem, 6873161Sjulian}; 6973161Sjulian 70181905Sedstatic void nmdm_task_tty(void *, int); 7173161Sjulian 72181905Sedstruct nmdmsoftc; 73140878Sphk 74181905Sedstruct nmdmpart { 75181905Sed struct tty *np_tty; 76181905Sed int np_dcd; 77181905Sed struct task np_task; 78181905Sed struct nmdmpart *np_other; 79181905Sed struct nmdmsoftc *np_pair; 80181905Sed struct callout np_callout; 81181905Sed u_long np_quota; 82181905Sed u_long np_accumulator; 83181905Sed int np_rate; 84181905Sed int np_credits; 85181905Sed 86140878Sphk#define QS 8 /* Quota shift */ 8773161Sjulian}; 8873161Sjulian 89181905Sedstruct nmdmsoftc { 90181905Sed struct nmdmpart ns_part1; 91181905Sed struct nmdmpart ns_part2; 92181905Sed struct mtx ns_mtx; 9373161Sjulian}; 9473161Sjulian 95181905Sedstatic int nmdm_count = 0; 96126077Sphk 97181905Sedstatic struct nmdmsoftc * 98181905Sednmdm_alloc(unsigned long unit) 99181905Sed{ 100181905Sed struct nmdmsoftc *ns; 101181905Sed struct tty *tp; 102181905Sed 103193828Sjhb atomic_add_int(&nmdm_count, 1); 104181905Sed 105181905Sed ns = malloc(sizeof(*ns), M_NMDM, M_WAITOK|M_ZERO); 106181905Sed mtx_init(&ns->ns_mtx, "nmdm", NULL, MTX_DEF); 107181905Sed 108181905Sed /* Hook the pairs together. */ 109181905Sed ns->ns_part1.np_pair = ns; 110181905Sed ns->ns_part1.np_other = &ns->ns_part2; 111181905Sed TASK_INIT(&ns->ns_part1.np_task, 0, nmdm_task_tty, &ns->ns_part1); 112193828Sjhb callout_init_mtx(&ns->ns_part1.np_callout, &ns->ns_mtx, 0); 113181905Sed 114181905Sed ns->ns_part2.np_pair = ns; 115181905Sed ns->ns_part2.np_other = &ns->ns_part1; 116181905Sed TASK_INIT(&ns->ns_part2.np_task, 0, nmdm_task_tty, &ns->ns_part2); 117193828Sjhb callout_init_mtx(&ns->ns_part2.np_callout, &ns->ns_mtx, 0); 118181905Sed 119181905Sed /* Create device nodes. */ 120193018Sed tp = ns->ns_part1.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part1, 121181905Sed &ns->ns_mtx); 122181905Sed tty_makedev(tp, NULL, "nmdm%luA", unit); 123181905Sed 124193018Sed tp = ns->ns_part2.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part2, 125181905Sed &ns->ns_mtx); 126181905Sed tty_makedev(tp, NULL, "nmdm%luB", unit); 127181905Sed 128181905Sed return (ns); 129181905Sed} 130181905Sed 13173161Sjulianstatic void 132148868Srwatsonnmdm_clone(void *arg, struct ucred *cred, char *name, int nameen, 133148868Srwatson struct cdev **dev) 134126077Sphk{ 135181905Sed unsigned long unit; 136181905Sed char *end; 137181905Sed struct nmdmsoftc *ns; 138126077Sphk 139130640Sphk if (*dev != NULL) 140126077Sphk return; 141181905Sed if (strncmp(name, "nmdm", 4) != 0) 142181905Sed return; 143181905Sed 144181905Sed /* Device name must be "nmdm%lu%c", where %c is 'A' or 'B'. */ 145181905Sed name += 4; 146181905Sed unit = strtoul(name, &end, 10); 147181905Sed if (unit == ULONG_MAX || name == end) 148181905Sed return; 149181905Sed if ((end[0] != 'A' && end[0] != 'B') || end[1] != '\0') 150181905Sed return; 151181905Sed 152181905Sed /* XXX: pass privileges? */ 153181905Sed ns = nmdm_alloc(unit); 154181905Sed 155181905Sed if (end[0] == 'A') 156181905Sed *dev = ns->ns_part1.np_tty->t_dev; 157126077Sphk else 158181905Sed *dev = ns->ns_part2.np_tty->t_dev; 159126077Sphk} 160126077Sphk 161126077Sphkstatic void 162140878Sphknmdm_timeout(void *arg) 163140878Sphk{ 164181905Sed struct nmdmpart *np = arg; 165140878Sphk 166181905Sed if (np->np_rate == 0) 167140878Sphk return; 168140878Sphk 169140878Sphk /* 170140878Sphk * Do a simple Floyd-Steinberg dither here to avoid FP math. 171140878Sphk * Wipe out unused quota from last tick. 172140878Sphk */ 173181905Sed np->np_accumulator += np->np_credits; 174181905Sed np->np_quota = np->np_accumulator >> QS; 175181905Sed np->np_accumulator &= ((1 << QS) - 1); 176140878Sphk 177181905Sed taskqueue_enqueue(taskqueue_swi, &np->np_task); 178181905Sed callout_reset(&np->np_callout, np->np_rate, nmdm_timeout, np); 179140878Sphk} 180140878Sphk 181140878Sphkstatic void 182129968Sphknmdm_task_tty(void *arg, int pending __unused) 183129968Sphk{ 184129968Sphk struct tty *tp, *otp; 185181905Sed struct nmdmpart *np = arg; 186181905Sed char c; 18773161Sjulian 188181905Sed tp = np->np_tty; 189181905Sed tty_lock(tp); 190181905Sed 191181905Sed otp = np->np_other->np_tty; 192129968Sphk KASSERT(otp != NULL, ("NULL otp in nmdmstart")); 193129968Sphk KASSERT(otp != tp, ("NULL otp == tp nmdmstart")); 194181905Sed if (np->np_other->np_dcd) { 195181905Sed if (!tty_opened(tp)) { 196181905Sed np->np_other->np_dcd = 0; 197181905Sed ttydisc_modem(otp, 0); 198129968Sphk } 199129968Sphk } else { 200181905Sed if (tty_opened(tp)) { 201181905Sed np->np_other->np_dcd = 1; 202181905Sed ttydisc_modem(otp, 1); 203129968Sphk } 204129968Sphk } 205181905Sed 206181905Sed while (ttydisc_rint_poll(otp) > 0) { 207181905Sed if (np->np_rate && !np->np_quota) 208181905Sed break; 209181905Sed if (ttydisc_getc(tp, &c, 1) != 1) 210181905Sed break; 211181905Sed np->np_quota--; 212181905Sed ttydisc_rint(otp, c, 0); 213129968Sphk } 214140878Sphk 215181905Sed ttydisc_rint_done(otp); 21673161Sjulian 217181905Sed tty_unlock(tp); 21873161Sjulian} 21973161Sjulian 220129968Sphkstatic int 221140878Sphkbits_per_char(struct termios *t) 222140878Sphk{ 223140878Sphk int bits; 224140878Sphk 225140878Sphk bits = 1; /* start bit */ 226140878Sphk switch (t->c_cflag & CSIZE) { 227140878Sphk case CS5: bits += 5; break; 228140878Sphk case CS6: bits += 6; break; 229140878Sphk case CS7: bits += 7; break; 230140878Sphk case CS8: bits += 8; break; 231140878Sphk } 232140878Sphk bits++; /* stop bit */ 233140878Sphk if (t->c_cflag & PARENB) 234140878Sphk bits++; 235140878Sphk if (t->c_cflag & CSTOPB) 236140878Sphk bits++; 237140878Sphk return (bits); 238140878Sphk} 239140878Sphk 240140878Sphkstatic int 241181905Sednmdm_param(struct tty *tp, struct termios *t) 242140878Sphk{ 243181905Sed struct nmdmpart *np = tty_softc(tp); 244140878Sphk struct tty *tp2; 245140878Sphk int bpc, rate, speed, i; 246140878Sphk 247181905Sed tp2 = np->np_other->np_tty; 248140878Sphk 249181905Sed if (!((t->c_cflag | tp2->t_termios.c_cflag) & CDSR_OFLOW)) { 250181905Sed np->np_rate = 0; 251181905Sed np->np_other->np_rate = 0; 252140878Sphk return (0); 253140878Sphk } 254140878Sphk 255140878Sphk /* 256140878Sphk * DSRFLOW one either side enables rate-simulation for both 257140878Sphk * directions. 258140878Sphk * NB: the two directions may run at different rates. 259140878Sphk */ 260140878Sphk 261140878Sphk /* Find the larger of the number of bits transmitted */ 262140878Sphk bpc = imax(bits_per_char(t), bits_per_char(&tp2->t_termios)); 263140878Sphk 264140878Sphk for (i = 0; i < 2; i++) { 265140878Sphk /* Use the slower of our receive and their transmit rate */ 266181905Sed speed = imin(tp2->t_termios.c_ospeed, t->c_ispeed); 267140878Sphk if (speed == 0) { 268181905Sed np->np_rate = 0; 269181905Sed np->np_other->np_rate = 0; 270140878Sphk return (0); 271140878Sphk } 272140878Sphk 273140878Sphk speed <<= QS; /* [bit/sec, scaled] */ 274140878Sphk speed /= bpc; /* [char/sec, scaled] */ 275140878Sphk rate = (hz << QS) / speed; /* [hz per callout] */ 276140878Sphk if (rate == 0) 277140878Sphk rate = 1; 278140878Sphk 279140878Sphk speed *= rate; 280140878Sphk speed /= hz; /* [(char/sec)/tick, scaled */ 281140878Sphk 282181905Sed np->np_credits = speed; 283181905Sed np->np_rate = rate; 284181905Sed callout_reset(&np->np_callout, rate, nmdm_timeout, np); 285140878Sphk 286140878Sphk /* 287140878Sphk * swap pointers for second pass so the other end gets 288140878Sphk * updated as well. 289140878Sphk */ 290181905Sed np = np->np_other; 291140878Sphk t = &tp2->t_termios; 292140878Sphk tp2 = tp; 293140878Sphk } 294181905Sed 295140878Sphk return (0); 296140878Sphk} 297140878Sphk 298140878Sphkstatic int 299181905Sednmdm_modem(struct tty *tp, int sigon, int sigoff) 300131579Sphk{ 301181905Sed struct nmdmpart *np = tty_softc(tp); 302181905Sed int i = 0; 303131579Sphk 304131579Sphk if (sigon || sigoff) { 305140878Sphk if (sigon & SER_DTR) 306181905Sed np->np_other->np_dcd = 1; 307140878Sphk if (sigoff & SER_DTR) 308181905Sed np->np_other->np_dcd = 0; 309181905Sed 310181905Sed ttydisc_modem(np->np_other->np_tty, np->np_other->np_dcd); 311181905Sed 312131579Sphk return (0); 313131579Sphk } else { 314181905Sed if (np->np_dcd) 315131579Sphk i |= SER_DCD; 316181905Sed if (np->np_other->np_dcd) 317131579Sphk i |= SER_DTR; 318181905Sed 319131579Sphk return (i); 320131579Sphk } 321131579Sphk} 322131579Sphk 32373161Sjulianstatic void 324181905Sednmdm_inwakeup(struct tty *tp) 32573161Sjulian{ 326181905Sed struct nmdmpart *np = tty_softc(tp); 32773161Sjulian 328181905Sed /* We can receive again, so wake up the other side. */ 329181905Sed taskqueue_enqueue(taskqueue_swi, &np->np_other->np_task); 33073161Sjulian} 33173161Sjulian 33273161Sjulianstatic void 333181905Sednmdm_outwakeup(struct tty *tp) 33473161Sjulian{ 335181905Sed struct nmdmpart *np = tty_softc(tp); 33673161Sjulian 337181905Sed /* We can transmit again, so wake up our side. */ 338181905Sed taskqueue_enqueue(taskqueue_swi, &np->np_task); 33973161Sjulian} 34073161Sjulian 34190995Sjulian/* 34290995Sjulian * Module handling 34390995Sjulian */ 34490995Sjulianstatic int 34590995Sjuliannmdm_modevent(module_t mod, int type, void *data) 34690995Sjulian{ 347126077Sphk static eventhandler_tag tag; 34873161Sjulian 34990995Sjulian switch(type) { 350126077Sphk case MOD_LOAD: 351126077Sphk tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000); 352126077Sphk if (tag == NULL) 353126077Sphk return (ENOMEM); 35490995Sjulian break; 35573161Sjulian 35690995Sjulian case MOD_SHUTDOWN: 357181905Sed break; 358181905Sed 35990995Sjulian case MOD_UNLOAD: 360181905Sed if (nmdm_count != 0) 361181905Sed return (EBUSY); 362126077Sphk EVENTHANDLER_DEREGISTER(dev_clone, tag); 36390995Sjulian break; 364181905Sed 36590995Sjulian default: 366181905Sed return (EOPNOTSUPP); 36790995Sjulian } 368181905Sed 369181905Sed return (0); 37090995Sjulian} 37173161Sjulian 37290995SjulianDEV_MODULE(nmdm, nmdm_modevent, NULL); 373