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