xref: /freebsd/sys/dev/snp/snp.c (revision f9218d3d4fd34f082473b3a021c6d4d109fb47cf)
1 /*
2  * Copyright (c) 1995 Ugen J.S.Antsilevich
3  *
4  * Redistribution and use in source forms, with and without modification,
5  * are permitted provided that this entire comment appears intact.
6  *
7  * Redistribution in binary form may occur without any restrictions.
8  * Obviously, it would be nice if you gave credit where credit is due
9  * but requiring it would be too onerous.
10  *
11  * This software is provided ``AS IS'' without any warranties of any kind.
12  *
13  * Snoop stuff.
14  *
15  * $FreeBSD$
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/filio.h>
21 #include <sys/malloc.h>
22 #include <sys/tty.h>
23 #include <sys/conf.h>
24 #include <sys/poll.h>
25 #include <sys/kernel.h>
26 #include <sys/queue.h>
27 #include <sys/snoop.h>
28 #include <sys/vnode.h>
29 
30 static	l_close_t	snplclose;
31 static	l_write_t	snplwrite;
32 static	d_open_t	snpopen;
33 static	d_close_t	snpclose;
34 static	d_read_t	snpread;
35 static	d_write_t	snpwrite;
36 static	d_ioctl_t	snpioctl;
37 static	d_poll_t	snppoll;
38 
39 #define CDEV_MAJOR 53
40 static struct cdevsw snp_cdevsw = {
41 	.d_open =	snpopen,
42 	.d_close =	snpclose,
43 	.d_read =	snpread,
44 	.d_write =	snpwrite,
45 	.d_ioctl =	snpioctl,
46 	.d_poll =	snppoll,
47 	.d_name =	"snp",
48 	.d_maj =	CDEV_MAJOR,
49 };
50 
51 static struct linesw snpdisc = {
52 	ttyopen,	snplclose,	ttread,		snplwrite,
53 	l_nullioctl,	ttyinput,	ttstart,	ttymodem
54 };
55 
56 /*
57  * This is the main snoop per-device structure.
58  */
59 struct snoop {
60 	LIST_ENTRY(snoop)	snp_list;	/* List glue. */
61 	int			snp_unit;	/* Device number. */
62 	dev_t			snp_target;	/* Target tty device. */
63 	struct tty		*snp_tty;	/* Target tty pointer. */
64 	u_long			 snp_len;	/* Possible length. */
65 	u_long			 snp_base;	/* Data base. */
66 	u_long			 snp_blen;	/* Used length. */
67 	caddr_t			 snp_buf;	/* Allocation pointer. */
68 	int			 snp_flags;	/* Flags. */
69 	struct selinfo		 snp_sel;	/* Select info. */
70 	int			 snp_olddisc;	/* Old line discipline. */
71 };
72 
73 /*
74  * Possible flags.
75  */
76 #define SNOOP_ASYNC		0x0002
77 #define SNOOP_OPEN		0x0004
78 #define SNOOP_RWAIT		0x0008
79 #define SNOOP_OFLOW		0x0010
80 #define SNOOP_DOWN		0x0020
81 
82 /*
83  * Other constants.
84  */
85 #define SNOOP_MINLEN		(4*1024)	/* This should be power of 2.
86 						 * 4K tested to be the minimum
87 						 * for which on normal tty
88 						 * usage there is no need to
89 						 * allocate more.
90 						 */
91 #define SNOOP_MAXLEN		(64*1024)	/* This one also,64K enough
92 						 * If we grow more,something
93 						 * really bad in this world..
94 						 */
95 
96 static MALLOC_DEFINE(M_SNP, "snp", "Snoop device data");
97 /*
98  * The number of the "snoop" line discipline.  This gets determined at
99  * module load time.
100  */
101 static int snooplinedisc;
102 static udev_t snpbasedev = NOUDEV;
103 
104 
105 static LIST_HEAD(, snoop) snp_sclist = LIST_HEAD_INITIALIZER(&snp_sclist);
106 
107 static struct tty	*snpdevtotty(dev_t dev);
108 static void		snp_clone(void *arg, char *name,
109 			    int namelen, dev_t *dev);
110 static int		snp_detach(struct snoop *snp);
111 static int		snp_down(struct snoop *snp);
112 static int		snp_in(struct snoop *snp, char *buf, int n);
113 static int		snp_modevent(module_t mod, int what, void *arg);
114 
115 static int
116 snplclose(tp, flag)
117 	struct tty *tp;
118 	int flag;
119 {
120 	struct snoop *snp;
121 	int error;
122 
123 	snp = tp->t_sc;
124 	error = snp_down(snp);
125 	if (error != 0)
126 		return (error);
127 	error = ttylclose(tp, flag);
128 	return (error);
129 }
130 
131 static int
132 snplwrite(tp, uio, flag)
133 	struct tty *tp;
134 	struct uio *uio;
135 	int flag;
136 {
137 	struct iovec iov;
138 	struct uio uio2;
139 	struct snoop *snp;
140 	int error, ilen;
141 	char *ibuf;
142 
143 	error = 0;
144 	ibuf = NULL;
145 	snp = tp->t_sc;
146 	while (uio->uio_resid > 0) {
147 		ilen = imin(512, uio->uio_resid);
148 		ibuf = malloc(ilen, M_SNP, M_WAITOK);
149 		error = uiomove(ibuf, ilen, uio);
150 		if (error != 0)
151 			break;
152 		snp_in(snp, ibuf, ilen);
153 		/* Hackish, but probably the least of all evils. */
154 		iov.iov_base = ibuf;
155 		iov.iov_len = ilen;
156 		uio2.uio_iov = &iov;
157 		uio2.uio_iovcnt = 1;
158 		uio2.uio_offset = 0;
159 		uio2.uio_resid = ilen;
160 		uio2.uio_segflg = UIO_SYSSPACE;
161 		uio2.uio_rw = UIO_WRITE;
162 		uio2.uio_td = uio->uio_td;
163 		error = ttwrite(tp, &uio2, flag);
164 		if (error != 0)
165 			break;
166 		free(ibuf, M_SNP);
167 		ibuf = NULL;
168 	}
169 	if (ibuf != NULL)
170 		free(ibuf, M_SNP);
171 	return (error);
172 }
173 
174 static struct tty *
175 snpdevtotty(dev)
176 	dev_t dev;
177 {
178 	struct cdevsw *cdp;
179 
180 	cdp = devsw(dev);
181 	if (cdp == NULL || (cdp->d_flags & D_TTY) == 0)
182 		return (NULL);
183 	return (dev->si_tty);
184 }
185 
186 #define SNP_INPUT_BUF	5	/* This is even too much, the maximal
187 				 * interactive mode write is 3 bytes
188 				 * length for function keys...
189 				 */
190 
191 static int
192 snpwrite(dev, uio, flag)
193 	dev_t dev;
194 	struct uio *uio;
195 	int flag;
196 {
197 	struct snoop *snp;
198 	struct tty *tp;
199 	int error, i, len;
200 	unsigned char c[SNP_INPUT_BUF];
201 
202 	snp = dev->si_drv1;
203 	tp = snp->snp_tty;
204 	if (tp == NULL)
205 		return (EIO);
206 	if ((tp->t_sc == snp) && (tp->t_state & TS_SNOOP) &&
207 	    tp->t_line == snooplinedisc)
208 		goto tty_input;
209 
210 	printf("snp%d: attempt to write to bad tty\n", snp->snp_unit);
211 	return (EIO);
212 
213 tty_input:
214 	if (!(tp->t_state & TS_ISOPEN))
215 		return (EIO);
216 
217 	while (uio->uio_resid > 0) {
218 		len = imin(uio->uio_resid, SNP_INPUT_BUF);
219 		if ((error = uiomove(c, len, uio)) != 0)
220 			return (error);
221 		for (i=0; i < len; i++) {
222 			if (ttyinput(c[i], tp))
223 				return (EIO);
224 		}
225 	}
226 	return (0);
227 }
228 
229 
230 static int
231 snpread(dev, uio, flag)
232 	dev_t dev;
233 	struct uio *uio;
234 	int flag;
235 {
236 	struct snoop *snp;
237 	int error, len, n, nblen, s;
238 	caddr_t from;
239 	char *nbuf;
240 
241 	snp = dev->si_drv1;
242 	KASSERT(snp->snp_len + snp->snp_base <= snp->snp_blen,
243 	    ("snoop buffer error"));
244 
245 	if (snp->snp_tty == NULL)
246 		return (EIO);
247 
248 	snp->snp_flags &= ~SNOOP_RWAIT;
249 
250 	do {
251 		if (snp->snp_len == 0) {
252 			if (flag & IO_NDELAY)
253 				return (EWOULDBLOCK);
254 			snp->snp_flags |= SNOOP_RWAIT;
255 			error = tsleep(snp, (PZERO + 1) | PCATCH,
256 			    "snprd", 0);
257 			if (error != 0)
258 				return (error);
259 		}
260 	} while (snp->snp_len == 0);
261 
262 	n = snp->snp_len;
263 
264 	error = 0;
265 	while (snp->snp_len > 0 && uio->uio_resid > 0 && error == 0) {
266 		len = min((unsigned)uio->uio_resid, snp->snp_len);
267 		from = (caddr_t)(snp->snp_buf + snp->snp_base);
268 		if (len == 0)
269 			break;
270 
271 		error = uiomove(from, len, uio);
272 		snp->snp_base += len;
273 		snp->snp_len -= len;
274 	}
275 	if ((snp->snp_flags & SNOOP_OFLOW) && (n < snp->snp_len)) {
276 		snp->snp_flags &= ~SNOOP_OFLOW;
277 	}
278 	s = spltty();
279 	nblen = snp->snp_blen;
280 	if (((nblen / 2) >= SNOOP_MINLEN) && (nblen / 2) >= snp->snp_len) {
281 		while (nblen / 2 >= snp->snp_len && nblen / 2 >= SNOOP_MINLEN)
282 			nblen = nblen / 2;
283 		if ((nbuf = malloc(nblen, M_SNP, M_NOWAIT)) != NULL) {
284 			bcopy(snp->snp_buf + snp->snp_base, nbuf, snp->snp_len);
285 			free(snp->snp_buf, M_SNP);
286 			snp->snp_buf = nbuf;
287 			snp->snp_blen = nblen;
288 			snp->snp_base = 0;
289 		}
290 	}
291 	splx(s);
292 
293 	return (error);
294 }
295 
296 static int
297 snp_in(snp, buf, n)
298 	struct snoop *snp;
299 	char *buf;
300 	int n;
301 {
302 	int s_free, s_tail;
303 	int s, len, nblen;
304 	caddr_t from, to;
305 	char *nbuf;
306 
307 	KASSERT(n >= 0, ("negative snoop char count"));
308 
309 	if (n == 0)
310 		return (0);
311 
312 	if (snp->snp_flags & SNOOP_DOWN) {
313 		printf("snp%d: more data to down interface\n", snp->snp_unit);
314 		return (0);
315 	}
316 
317 	if (snp->snp_flags & SNOOP_OFLOW) {
318 		printf("snp%d: buffer overflow\n", snp->snp_unit);
319 		/*
320 		 * On overflow we just repeat the standart close
321 		 * procedure...yes , this is waste of space but.. Then next
322 		 * read from device will fail if one would recall he is
323 		 * snooping and retry...
324 		 */
325 
326 		return (snp_down(snp));
327 	}
328 	s_tail = snp->snp_blen - (snp->snp_len + snp->snp_base);
329 	s_free = snp->snp_blen - snp->snp_len;
330 
331 
332 	if (n > s_free) {
333 		s = spltty();
334 		nblen = snp->snp_blen;
335 		while ((n > s_free) && ((nblen * 2) <= SNOOP_MAXLEN)) {
336 			nblen = snp->snp_blen * 2;
337 			s_free = nblen - (snp->snp_len + snp->snp_base);
338 		}
339 		if ((n <= s_free) && (nbuf = malloc(nblen, M_SNP, M_NOWAIT))) {
340 			bcopy(snp->snp_buf + snp->snp_base, nbuf, snp->snp_len);
341 			free(snp->snp_buf, M_SNP);
342 			snp->snp_buf = nbuf;
343 			snp->snp_blen = nblen;
344 			snp->snp_base = 0;
345 		} else {
346 			snp->snp_flags |= SNOOP_OFLOW;
347 			if (snp->snp_flags & SNOOP_RWAIT) {
348 				snp->snp_flags &= ~SNOOP_RWAIT;
349 				wakeup(snp);
350 			}
351 			splx(s);
352 			return (0);
353 		}
354 		splx(s);
355 	}
356 	if (n > s_tail) {
357 		from = (caddr_t)(snp->snp_buf + snp->snp_base);
358 		to = (caddr_t)(snp->snp_buf);
359 		len = snp->snp_len;
360 		bcopy(from, to, len);
361 		snp->snp_base = 0;
362 	}
363 	to = (caddr_t)(snp->snp_buf + snp->snp_base + snp->snp_len);
364 	bcopy(buf, to, n);
365 	snp->snp_len += n;
366 
367 	if (snp->snp_flags & SNOOP_RWAIT) {
368 		snp->snp_flags &= ~SNOOP_RWAIT;
369 		wakeup(snp);
370 	}
371 	selwakeup(&snp->snp_sel);
372 
373 	return (n);
374 }
375 
376 static int
377 snpopen(dev, flag, mode, td)
378 	dev_t dev;
379 	int flag, mode;
380 	struct thread *td;
381 {
382 	struct snoop *snp;
383 
384 	if (dev->si_drv1 == NULL) {
385 		if (!(dev->si_flags & SI_NAMED))
386 			make_dev(&snp_cdevsw, minor(dev), UID_ROOT, GID_WHEEL,
387 			    0600, "snp%d", dev2unit(dev));
388 		dev->si_drv1 = snp = malloc(sizeof(*snp), M_SNP,
389 		    M_WAITOK | M_ZERO);
390 		snp->snp_unit = dev2unit(dev);
391 	} else
392 		return (EBUSY);
393 
394 	/*
395 	 * We intentionally do not OR flags with SNOOP_OPEN, but set them so
396 	 * all previous settings (especially SNOOP_OFLOW) will be cleared.
397 	 */
398 	snp->snp_flags = SNOOP_OPEN;
399 
400 	snp->snp_buf = malloc(SNOOP_MINLEN, M_SNP, M_WAITOK);
401 	snp->snp_blen = SNOOP_MINLEN;
402 	snp->snp_base = 0;
403 	snp->snp_len = 0;
404 
405 	/*
406 	 * snp_tty == NULL  is for inactive snoop devices.
407 	 */
408 	snp->snp_tty = NULL;
409 	snp->snp_target = NODEV;
410 
411 	LIST_INSERT_HEAD(&snp_sclist, snp, snp_list);
412 	return (0);
413 }
414 
415 
416 static int
417 snp_detach(snp)
418 	struct snoop *snp;
419 {
420 	struct tty *tp;
421 
422 	snp->snp_base = 0;
423 	snp->snp_len = 0;
424 
425 	/*
426 	 * If line disc. changed we do not touch this pointer, SLIP/PPP will
427 	 * change it anyway.
428 	 */
429 	tp = snp->snp_tty;
430 	if (tp == NULL)
431 		goto detach_notty;
432 
433 	if (tp && (tp->t_sc == snp) && (tp->t_state & TS_SNOOP) &&
434 	    tp->t_line == snooplinedisc) {
435 		tp->t_sc = NULL;
436 		tp->t_state &= ~TS_SNOOP;
437 		tp->t_line = snp->snp_olddisc;
438 	} else
439 		printf("snp%d: bad attached tty data\n", snp->snp_unit);
440 
441 	snp->snp_tty = NULL;
442 	snp->snp_target = NODEV;
443 
444 detach_notty:
445 	selwakeup(&snp->snp_sel);
446 	if ((snp->snp_flags & SNOOP_OPEN) == 0)
447 		free(snp, M_SNP);
448 
449 	return (0);
450 }
451 
452 static int
453 snpclose(dev, flags, fmt, td)
454 	dev_t dev;
455 	int flags;
456 	int fmt;
457 	struct thread *td;
458 {
459 	struct snoop *snp;
460 
461 	snp = dev->si_drv1;
462 	snp->snp_blen = 0;
463 	LIST_REMOVE(snp, snp_list);
464 	free(snp->snp_buf, M_SNP);
465 	snp->snp_flags &= ~SNOOP_OPEN;
466 	dev->si_drv1 = NULL;
467 
468 	return (snp_detach(snp));
469 }
470 
471 static int
472 snp_down(snp)
473 	struct snoop *snp;
474 {
475 
476 	if (snp->snp_blen != SNOOP_MINLEN) {
477 		free(snp->snp_buf, M_SNP);
478 		snp->snp_buf = malloc(SNOOP_MINLEN, M_SNP, M_WAITOK);
479 		snp->snp_blen = SNOOP_MINLEN;
480 	}
481 	snp->snp_flags |= SNOOP_DOWN;
482 
483 	return (snp_detach(snp));
484 }
485 
486 static int
487 snpioctl(dev, cmd, data, flags, td)
488 	dev_t dev;
489 	u_long cmd;
490 	caddr_t data;
491 	int flags;
492 	struct thread *td;
493 {
494 	struct snoop *snp;
495 	struct tty *tp, *tpo;
496 	dev_t tdev;
497 	int s;
498 
499 	snp = dev->si_drv1;
500 	switch (cmd) {
501 	case SNPSTTY:
502 		tdev = udev2dev(*((udev_t *)data), 0);
503 		if (tdev == NODEV)
504 			return (snp_down(snp));
505 
506 		tp = snpdevtotty(tdev);
507 		if (!tp)
508 			return (EINVAL);
509 		if (tp->t_state & TS_SNOOP)
510 			return (EBUSY);
511 
512 		s = spltty();
513 
514 		if (snp->snp_target == NODEV) {
515 			tpo = snp->snp_tty;
516 			if (tpo)
517 				tpo->t_state &= ~TS_SNOOP;
518 		}
519 
520 		tp->t_sc = (caddr_t)snp;
521 		tp->t_state |= TS_SNOOP;
522 		snp->snp_olddisc = tp->t_line;
523 		tp->t_line = snooplinedisc;
524 		snp->snp_tty = tp;
525 		snp->snp_target = tdev;
526 
527 		/*
528 		 * Clean overflow and down flags -
529 		 * we'll have a chance to get them in the future :)))
530 		 */
531 		snp->snp_flags &= ~SNOOP_OFLOW;
532 		snp->snp_flags &= ~SNOOP_DOWN;
533 		splx(s);
534 		break;
535 
536 	case SNPGTTY:
537 		/*
538 		 * We keep snp_target field specially to make
539 		 * SNPGTTY happy, else we can't know what is device
540 		 * major/minor for tty.
541 		 */
542 		*((udev_t *)data) = dev2udev(snp->snp_target);
543 		break;
544 
545 	case FIONBIO:
546 		break;
547 
548 	case FIOASYNC:
549 		if (*(int *)data)
550 			snp->snp_flags |= SNOOP_ASYNC;
551 		else
552 			snp->snp_flags &= ~SNOOP_ASYNC;
553 		break;
554 
555 	case FIONREAD:
556 		s = spltty();
557 		if (snp->snp_tty != NULL)
558 			*(int *)data = snp->snp_len;
559 		else
560 			if (snp->snp_flags & SNOOP_DOWN) {
561 				if (snp->snp_flags & SNOOP_OFLOW)
562 					*(int *)data = SNP_OFLOW;
563 				else
564 					*(int *)data = SNP_TTYCLOSE;
565 			} else {
566 				*(int *)data = SNP_DETACH;
567 			}
568 		splx(s);
569 		break;
570 
571 	default:
572 		return (ENOTTY);
573 	}
574 	return (0);
575 }
576 
577 static int
578 snppoll(dev, events, td)
579 	dev_t dev;
580 	int events;
581 	struct thread *td;
582 {
583 	struct snoop *snp;
584 	int revents;
585 
586 	snp = dev->si_drv1;
587 	revents = 0;
588 	/*
589 	 * If snoop is down, we don't want to poll() forever so we return 1.
590 	 * Caller should see if we down via FIONREAD ioctl().  The last should
591 	 * return -1 to indicate down state.
592 	 */
593 	if (events & (POLLIN | POLLRDNORM)) {
594 		if (snp->snp_flags & SNOOP_DOWN || snp->snp_len > 0)
595 			revents |= events & (POLLIN | POLLRDNORM);
596 		else
597 			selrecord(td, &snp->snp_sel);
598 	}
599 	return (revents);
600 }
601 
602 static void
603 snp_clone(arg, name, namelen, dev)
604 	void *arg;
605 	char *name;
606 	int namelen;
607 	dev_t *dev;
608 {
609 	int u;
610 
611 	if (*dev != NODEV)
612 		return;
613 	if (dev_stdclone(name, NULL, "snp", &u) != 1)
614 		return;
615 	*dev = make_dev(&snp_cdevsw, unit2minor(u), UID_ROOT, GID_WHEEL, 0600,
616 	    "snp%d", u);
617 	if (snpbasedev == NOUDEV)
618 		snpbasedev = (*dev)->si_udev;
619 	else {
620 		(*dev)->si_flags |= SI_CHEAPCLONE;
621 		dev_depends(udev2dev(snpbasedev, 0), *dev);
622 	}
623 }
624 
625 static int
626 snp_modevent(mod, type, data)
627 	module_t mod;
628 	int type;
629 	void *data;
630 {
631 	static eventhandler_tag eh_tag;
632 
633 	switch (type) {
634 	case MOD_LOAD:
635 		/* XXX error checking. */
636 		eh_tag = EVENTHANDLER_REGISTER(dev_clone, snp_clone, 0, 1000);
637 		snooplinedisc = ldisc_register(LDISC_LOAD, &snpdisc);
638 		break;
639 	case MOD_UNLOAD:
640 		if (!LIST_EMPTY(&snp_sclist))
641 			return (EBUSY);
642 		EVENTHANDLER_DEREGISTER(dev_clone, eh_tag);
643 		if (snpbasedev != NOUDEV)
644 			destroy_dev(udev2dev(snpbasedev, 0));
645 		ldisc_deregister(snooplinedisc);
646 		break;
647 	default:
648 		break;
649 	}
650 	return (0);
651 }
652 
653 static moduledata_t snp_mod = {
654         "snp",
655         snp_modevent,
656         NULL
657 };
658 DECLARE_MODULE(snp, snp_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE + CDEV_MAJOR);
659