nmdm.c revision 139192
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 139192 2004-12-22 17:28:34Z 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#include <sys/proc.h> 45#include <sys/tty.h> 46#include <sys/conf.h> 47#include <sys/fcntl.h> 48#include <sys/poll.h> 49#include <sys/kernel.h> 50#include <sys/module.h> 51#include <sys/serial.h> 52#include <sys/signalvar.h> 53#include <sys/malloc.h> 54#include <sys/taskqueue.h> 55 56MALLOC_DEFINE(M_NLMDM, "nullmodem", "nullmodem data structures"); 57 58static void nmdmstart(struct tty *tp); 59static void nmdmstop(struct tty *tp, int rw); 60static void nmdminit(struct cdev *dev); 61static t_modem_t nmdmmodem; 62 63static d_open_t nmdmopen; 64static d_close_t nmdmclose; 65 66static struct cdevsw nmdm_cdevsw = { 67 .d_version = D_VERSION, 68 .d_open = nmdmopen, 69 .d_close = nmdmclose, 70 .d_name = "nmdn", 71 .d_flags = D_TTY | D_PSEUDO | D_NEEDGIANT, 72}; 73 74#define BUFSIZ 100 /* Chunk size iomoved to/from user */ 75#define NMDM_MAX_NUM 128 /* Artificially limit # devices. */ 76#define PF_STOPPED 0x10 /* user told stopped */ 77#define BFLAG CLONE_FLAG0 78 79struct softpart { 80 struct tty *nm_tty; 81 struct cdev *dev; 82 int nm_dcd; 83 struct task pt_task; 84 struct softpart *other; 85}; 86 87struct nm_softc { 88 TAILQ_ENTRY(nm_softc) pt_list; 89 int pt_flags; 90 struct softpart part1, part2; 91 struct prison *pt_prison; 92}; 93 94static struct clonedevs *nmdmclones; 95static TAILQ_HEAD(,nm_softc) nmdmhead = TAILQ_HEAD_INITIALIZER(nmdmhead); 96 97static void 98nmdm_clone(void *arg, char *name, int nameen, struct cdev **dev) 99{ 100 int i, unit; 101 char *p; 102 struct cdev *d1, *d2; 103 104 if (*dev != NULL) 105 return; 106 if (strcmp(name, "nmdm") == 0) { 107 p = NULL; 108 unit = -1; 109 } else { 110 i = dev_stdclone(name, &p, "nmdm", &unit); 111 if (i == 0) 112 return; 113 if (p[0] != '\0' && p[0] != 'A' && p[0] != 'B') 114 return; 115 else if (p[0] != '\0' && p[1] != '\0') 116 return; 117 } 118 i = clone_create(&nmdmclones, &nmdm_cdevsw, &unit, &d1, 0); 119 if (i) { 120 d1 = make_dev(&nmdm_cdevsw, unit2minor(unit), 121 0, 0, 0666, "nmdm%dA", unit); 122 if (d1 == NULL) 123 return; 124 d2 = make_dev(&nmdm_cdevsw, unit2minor(unit) | BFLAG, 125 0, 0, 0666, "nmdm%dB", unit); 126 if (d2 == NULL) { 127 destroy_dev(d1); 128 return; 129 } 130 d2->si_drv2 = d1; 131 d1->si_drv2 = d2; 132 dev_depends(d1, d2); 133 dev_depends(d2, d1); 134 d1->si_flags |= SI_CHEAPCLONE; 135 d2->si_flags |= SI_CHEAPCLONE; 136 } 137 if (p != NULL && p[0] == 'B') 138 *dev = d1->si_drv2; 139 else 140 *dev = d1; 141} 142 143static void 144nmdm_task_tty(void *arg, int pending __unused) 145{ 146 struct tty *tp, *otp; 147 struct softpart *sp; 148 int c; 149 150 tp = arg; 151 sp = tp->t_sc; 152 otp = sp->other->nm_tty; 153 KASSERT(otp != NULL, ("NULL otp in nmdmstart")); 154 KASSERT(otp != tp, ("NULL otp == tp nmdmstart")); 155 if (sp->other->nm_dcd) { 156 if (!(tp->t_state & TS_ISOPEN)) { 157 sp->other->nm_dcd = 0; 158 (void)ttyld_modem(otp, 0); 159 } 160 } else { 161 if (tp->t_state & TS_ISOPEN) { 162 sp->other->nm_dcd = 1; 163 (void)ttyld_modem(otp, 1); 164 } 165 } 166 if (tp->t_state & TS_TTSTOP) 167 return; 168 while (tp->t_outq.c_cc != 0) { 169 if (otp->t_state & TS_TBLOCK) 170 return; 171 c = getc(&tp->t_outq); 172 if (otp->t_state & TS_ISOPEN) 173 ttyld_rint(otp, c); 174 } 175 if (tp->t_outq.c_cc == 0) 176 ttwwakeup(tp); 177} 178 179/* 180 * This function creates and initializes a pair of ttys. 181 */ 182static void 183nmdminit(struct cdev *dev1) 184{ 185 struct cdev *dev2; 186 struct nm_softc *pt; 187 188 dev2 = dev1->si_drv2; 189 190 dev1->si_flags &= ~SI_CHEAPCLONE; 191 dev2->si_flags &= ~SI_CHEAPCLONE; 192 193 pt = malloc(sizeof(*pt), M_NLMDM, M_WAITOK | M_ZERO); 194 TAILQ_INSERT_TAIL(&nmdmhead, pt, pt_list); 195 196 dev1->si_drv1 = dev2->si_drv1 = pt; 197 198 pt->part1.dev = dev1; 199 pt->part2.dev = dev2; 200 201 pt->part1.nm_tty = ttymalloc(pt->part1.nm_tty); 202 pt->part1.nm_tty->t_oproc = nmdmstart; 203 pt->part1.nm_tty->t_stop = nmdmstop; 204 pt->part1.nm_tty->t_modem = nmdmmodem; 205 pt->part1.nm_tty->t_dev = dev1; 206 pt->part1.nm_tty->t_sc = &pt->part1; 207 TASK_INIT(&pt->part1.pt_task, 0, nmdm_task_tty, pt->part1.nm_tty); 208 209 pt->part2.nm_tty = ttymalloc(pt->part2.nm_tty); 210 pt->part2.nm_tty->t_oproc = nmdmstart; 211 pt->part2.nm_tty->t_stop = nmdmstop; 212 pt->part2.nm_tty->t_modem = nmdmmodem; 213 pt->part2.nm_tty->t_dev = dev2; 214 pt->part2.nm_tty->t_sc = &pt->part2; 215 TASK_INIT(&pt->part2.pt_task, 0, nmdm_task_tty, pt->part2.nm_tty); 216 217 pt->part1.other = &pt->part2; 218 pt->part2.other = &pt->part1; 219 220 dev1->si_tty = pt->part1.nm_tty; 221 dev1->si_drv1 = pt; 222 223 dev2->si_tty = pt->part2.nm_tty; 224 dev2->si_drv1 = pt; 225} 226 227/* 228 * Device opened from userland 229 */ 230static int 231nmdmopen(struct cdev *dev, int flag, int devtype, struct thread *td) 232{ 233 struct tty *tp, *tp2; 234 int error; 235 struct nm_softc *pti; 236 struct softpart *sp; 237 238 if (dev->si_drv1 == NULL) 239 nmdminit(dev); 240 pti = dev->si_drv1; 241 if (pti->pt_prison != td->td_ucred->cr_prison) 242 return (EBUSY); 243 244 tp = dev->si_tty; 245 sp = tp->t_sc; 246 tp2 = sp->other->nm_tty; 247 248 if ((tp->t_state & TS_ISOPEN) == 0) { 249 ttyinitmode(tp, 0, 0); 250 ttsetwater(tp); /* XXX ? */ 251 } else if (tp->t_state & TS_XCLUDE && suser(td)) { 252 return (EBUSY); 253 } 254 255 error = ttyld_open(tp, dev); 256 return (error); 257} 258 259static int 260nmdmmodem(struct tty *tp, int sigon, int sigoff) 261{ 262 struct softpart *sp; 263 int i; 264 265 sp = tp->t_sc; 266 if (sigon || sigoff) { 267 if (sigon & SER_DTR) { 268 sp->other->nm_dcd = 1; 269 ttyld_modem(sp->other->nm_tty, sp->other->nm_dcd); 270 } 271 if (sigoff & SER_DTR) { 272 sp->other->nm_dcd = 0; 273 ttyld_modem(sp->other->nm_tty, sp->other->nm_dcd); 274 } 275 return (0); 276 } else { 277 i = 0; 278 if (sp->nm_dcd) 279 i |= SER_DCD; 280 if (sp->other->nm_dcd) 281 i |= SER_DTR; 282 return (i); 283 } 284} 285 286static int 287nmdmclose(struct cdev *dev, int flag, int mode, struct thread *td) 288{ 289 290 return (tty_close(dev->si_tty)); 291} 292 293static void 294nmdmstart(struct tty *tp) 295{ 296 struct softpart *pt; 297 298 pt = tp->t_sc; 299 taskqueue_enqueue(taskqueue_swi_giant, &pt->pt_task); 300} 301 302static void 303nmdmstop(struct tty *tp, int flush) 304{ 305 struct softpart *pt; 306 307 pt = tp->t_sc; 308 taskqueue_enqueue(taskqueue_swi_giant, &pt->pt_task); 309} 310 311/* 312 * Module handling 313 */ 314static int 315nmdm_modevent(module_t mod, int type, void *data) 316{ 317 static eventhandler_tag tag; 318 struct nm_softc *pt, *tpt; 319 int error = 0; 320 321 switch(type) { 322 case MOD_LOAD: 323 clone_setup(&nmdmclones); 324 tag = EVENTHANDLER_REGISTER(dev_clone, nmdm_clone, 0, 1000); 325 if (tag == NULL) 326 return (ENOMEM); 327 break; 328 329 case MOD_SHUTDOWN: 330 /* FALLTHROUGH */ 331 case MOD_UNLOAD: 332 EVENTHANDLER_DEREGISTER(dev_clone, tag); 333 TAILQ_FOREACH_SAFE(pt, &nmdmhead, pt_list, tpt) { 334 destroy_dev(pt->part1.dev); 335 TAILQ_REMOVE(&nmdmhead, pt, pt_list); 336 free(pt, M_NLMDM); 337 } 338 clone_cleanup(&nmdmclones); 339 break; 340 default: 341 error = EOPNOTSUPP; 342 } 343 return (error); 344} 345 346DEV_MODULE(nmdm, nmdm_modevent, NULL); 347