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 <sys/param.h> 40 #include <sys/systm.h> 41 #include <sys/priv.h> 42 #include <sys/proc.h> 43 #include <sys/tty.h> 44 #include <sys/conf.h> 45 #include <sys/fcntl.h> 46 #include <sys/poll.h> 47 #include <sys/kernel.h> 48 #include <sys/limits.h> 49 #include <sys/module.h> 50 #include <sys/serial.h> 51 #include <sys/signalvar.h> 52 #include <sys/malloc.h> 53 #include <sys/taskqueue.h> 54 55 static MALLOC_DEFINE(M_NMDM, "nullmodem", "nullmodem data structures"); 56 57 static tsw_inwakeup_t nmdm_outwakeup; 58 static tsw_outwakeup_t nmdm_inwakeup; 59 static tsw_param_t nmdm_param; 60 static tsw_modem_t nmdm_modem; 61 62 static struct ttydevsw nmdm_class = { 63 .tsw_flags = TF_NOPREFIX, 64 .tsw_inwakeup = nmdm_inwakeup, 65 .tsw_outwakeup = nmdm_outwakeup, 66 .tsw_param = nmdm_param, 67 .tsw_modem = nmdm_modem, 68 }; 69 70 static void nmdm_task_tty(void *, int); 71 72 struct nmdmsoftc; 73 74 struct nmdmpart { 75 struct tty *np_tty; 76 int np_dcd; 77 struct task np_task; 78 struct nmdmpart *np_other; 79 struct nmdmsoftc *np_pair; 80 struct callout np_callout; 81 u_long np_quota; 82 u_long np_accumulator; 83 int np_rate; 84 int np_credits; 85 86 #define QS 8 /* Quota shift */ 87 }; 88 89 struct nmdmsoftc { 90 struct nmdmpart ns_part1; 91 struct nmdmpart ns_part2; 92 struct mtx ns_mtx; 93 }; 94 95 static int nmdm_count = 0; 96 97 static struct nmdmsoftc * 98 nmdm_alloc(unsigned long unit) 99 { 100 struct nmdmsoftc *ns; 101 struct tty *tp; 102 103 atomic_add_int(&nmdm_count, 1); 104 105 ns = malloc(sizeof(*ns), M_NMDM, M_WAITOK|M_ZERO); 106 mtx_init(&ns->ns_mtx, "nmdm", NULL, MTX_DEF); 107 108 /* Hook the pairs together. */ 109 ns->ns_part1.np_pair = ns; 110 ns->ns_part1.np_other = &ns->ns_part2; 111 TASK_INIT(&ns->ns_part1.np_task, 0, nmdm_task_tty, &ns->ns_part1); 112 callout_init_mtx(&ns->ns_part1.np_callout, &ns->ns_mtx, 0); 113 114 ns->ns_part2.np_pair = ns; 115 ns->ns_part2.np_other = &ns->ns_part1; 116 TASK_INIT(&ns->ns_part2.np_task, 0, nmdm_task_tty, &ns->ns_part2); 117 callout_init_mtx(&ns->ns_part2.np_callout, &ns->ns_mtx, 0); 118 119 /* Create device nodes. */ 120 tp = ns->ns_part1.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part1, 121 &ns->ns_mtx); 122 tty_makedev(tp, NULL, "nmdm%luA", unit); 123 124 tp = ns->ns_part2.np_tty = tty_alloc_mutex(&nmdm_class, &ns->ns_part2, 125 &ns->ns_mtx); 126 tty_makedev(tp, NULL, "nmdm%luB", unit); 127 128 return (ns); 129 } 130 131 static void 132 nmdm_clone(void *arg, struct ucred *cred, char *name, int nameen, 133 struct cdev **dev) 134 { 135 unsigned long unit; 136 char *end; 137 struct nmdmsoftc *ns; 138 139 if (*dev != NULL) 140 return; 141 if (strncmp(name, "nmdm", 4) != 0) 142 return; 143 144 /* Device name must be "nmdm%lu%c", where %c is 'A' or 'B'. */ 145 name += 4; 146 unit = strtoul(name, &end, 10); 147 if (unit == ULONG_MAX || name == end) 148 return; 149 if ((end[0] != 'A' && end[0] != 'B') || end[1] != '\0') 150 return; 151 152 /* XXX: pass privileges? */ 153 ns = nmdm_alloc(unit); 154 155 if (end[0] == 'A') 156 *dev = ns->ns_part1.np_tty->t_dev; 157 else 158 *dev = ns->ns_part2.np_tty->t_dev; 159 } 160 161 static void 162 nmdm_timeout(void *arg) 163 { 164 struct nmdmpart *np = arg; 165 166 if (np->np_rate == 0) 167 return; 168 169 /* 170 * Do a simple Floyd-Steinberg dither here to avoid FP math. 171 * Wipe out unused quota from last tick. 172 */ 173 np->np_accumulator += np->np_credits; 174 np->np_quota = np->np_accumulator >> QS; 175 np->np_accumulator &= ((1 << QS) - 1); 176 177 taskqueue_enqueue(taskqueue_swi, &np->np_task); 178 callout_reset(&np->np_callout, np->np_rate, nmdm_timeout, np); 179 } 180 181 static void 182 nmdm_task_tty(void *arg, int pending __unused) 183 { 184 struct tty *tp, *otp; 185 struct nmdmpart *np = arg; 186 char c; 187 188 tp = np->np_tty; 189 tty_lock(tp); 190 191 otp = np->np_other->np_tty; 192 KASSERT(otp != NULL, ("NULL otp in nmdmstart")); 193 KASSERT(otp != tp, ("NULL otp == tp nmdmstart")); 194 if (np->np_other->np_dcd) { 195 if (!tty_opened(tp)) { 196 np->np_other->np_dcd = 0; 197 ttydisc_modem(otp, 0); 198 } 199 } else { 200 if (tty_opened(tp)) { 201 np->np_other->np_dcd = 1; 202 ttydisc_modem(otp, 1); 203 } 204 } 205 206 while (ttydisc_rint_poll(otp) > 0) { 207 if (np->np_rate && !np->np_quota) 208 break; 209 if (ttydisc_getc(tp, &c, 1) != 1) 210 break; 211 np->np_quota--; 212 ttydisc_rint(otp, c, 0); 213 } 214 215 ttydisc_rint_done(otp); 216 217 tty_unlock(tp); 218 } 219 220 static int 221 bits_per_char(struct termios *t) 222 { 223 int bits; 224 225 bits = 1; /* start bit */ 226 switch (t->c_cflag & CSIZE) { 227 case CS5: bits += 5; break; 228 case CS6: bits += 6; break; 229 case CS7: bits += 7; break; 230 case CS8: bits += 8; break; 231 } 232 bits++; /* stop bit */ 233 if (t->c_cflag & PARENB) 234 bits++; 235 if (t->c_cflag & CSTOPB) 236 bits++; 237 return (bits); 238 } 239 240 static int 241 nmdm_param(struct tty *tp, struct termios *t) 242 { 243 struct nmdmpart *np = tty_softc(tp); 244 struct tty *tp2; 245 int bpc, rate, speed, i; 246 247 tp2 = np->np_other->np_tty; 248 249 if (!((t->c_cflag | tp2->t_termios.c_cflag) & CDSR_OFLOW)) { 250 np->np_rate = 0; 251 np->np_other->np_rate = 0; 252 return (0); 253 } 254 255 /* 256 * DSRFLOW one either side enables rate-simulation for both 257 * directions. 258 * NB: the two directions may run at different rates. 259 */ 260 261 /* Find the larger of the number of bits transmitted */ 262 bpc = imax(bits_per_char(t), bits_per_char(&tp2->t_termios)); 263 264 for (i = 0; i < 2; i++) { 265 /* Use the slower of our receive and their transmit rate */ 266 speed = imin(tp2->t_termios.c_ospeed, t->c_ispeed); 267 if (speed == 0) { 268 np->np_rate = 0; 269 np->np_other->np_rate = 0; 270 return (0); 271 } 272 273 speed <<= QS; /* [bit/sec, scaled] */ 274 speed /= bpc; /* [char/sec, scaled] */ 275 rate = (hz << QS) / speed; /* [hz per callout] */ 276 if (rate == 0) 277 rate = 1; 278 279 speed *= rate; 280 speed /= hz; /* [(char/sec)/tick, scaled */ 281 282 np->np_credits = speed; 283 np->np_rate = rate; 284 callout_reset(&np->np_callout, rate, nmdm_timeout, np); 285 286 /* 287 * swap pointers for second pass so the other end gets 288 * updated as well. 289 */ 290 np = np->np_other; 291 t = &tp2->t_termios; 292 tp2 = tp; 293 } 294 295 return (0); 296 } 297 298 static int 299 nmdm_modem(struct tty *tp, int sigon, int sigoff) 300 { 301 struct nmdmpart *np = tty_softc(tp); 302 int i = 0; 303 304 if (sigon || sigoff) { 305 if (sigon & SER_DTR) 306 np->np_other->np_dcd = 1; 307 if (sigoff & SER_DTR) 308 np->np_other->np_dcd = 0; 309 310 ttydisc_modem(np->np_other->np_tty, np->np_other->np_dcd); 311 312 return (0); 313 } else { 314 if (np->np_dcd) 315 i |= SER_DCD; 316 if (np->np_other->np_dcd) 317 i |= SER_DTR; 318 319 return (i); 320 } 321 } 322 323 static void 324 nmdm_inwakeup(struct tty *tp) 325 { 326 struct nmdmpart *np = tty_softc(tp); 327 328 /* We can receive again, so wake up the other side. */ 329 taskqueue_enqueue(taskqueue_swi, &np->np_other->np_task); 330 } 331 332 static void 333 nmdm_outwakeup(struct tty *tp) 334 { 335 struct nmdmpart *np = tty_softc(tp); 336 337 /* We can transmit again, so wake up our side. */ 338 taskqueue_enqueue(taskqueue_swi, &np->np_task); 339 } 340 341 /* 342 * Module handling 343 */ 344 static int 345 nmdm_modevent(module_t mod, int type, void *data) 346 { 347 static eventhandler_tag tag; 348 349 switch(type) { 350 case MOD_LOAD: 351 tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000); 352 if (tag == NULL) 353 return (ENOMEM); 354 break; 355 356 case MOD_SHUTDOWN: 357 break; 358 359 case MOD_UNLOAD: 360 if (nmdm_count != 0) 361 return (EBUSY); 362 EVENTHANDLER_DEREGISTER(dev_clone, tag); 363 break; 364 365 default: 366 return (EOPNOTSUPP); 367 } 368 369 return (0); 370 } 371 372 DEV_MODULE(nmdm, nmdm_modevent, NULL); 373