diff options
| author | cvs2svn <cvs2svn@FreeBSD.org> | 2001-11-24 15:59:47 +0000 |
|---|---|---|
| committer | cvs2svn <cvs2svn@FreeBSD.org> | 2001-11-24 15:59:47 +0000 |
| commit | aefd32e210193600773b60f72b48a26ac3df8f7f (patch) | |
| tree | 0bb7201b4cfdd63aa949c1ba9fb9a34d6ded585e /sys/dev/snp | |
| parent | a195bd25b79a88541a7f4b61fb21d1e748aeb4ce (diff) | |
Notes
Diffstat (limited to 'sys/dev/snp')
| -rw-r--r-- | sys/dev/snp/snp.c | 665 |
1 files changed, 665 insertions, 0 deletions
diff --git a/sys/dev/snp/snp.c b/sys/dev/snp/snp.c new file mode 100644 index 000000000000..8c900d88a819 --- /dev/null +++ b/sys/dev/snp/snp.c @@ -0,0 +1,665 @@ +/* + * Copyright (c) 1995 Ugen J.S.Antsilevich + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * Snoop stuff. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/filio.h> +#include <sys/malloc.h> +#include <sys/tty.h> +#include <sys/conf.h> +#include <sys/poll.h> +#include <sys/kernel.h> +#include <sys/queue.h> +#include <sys/snoop.h> +#include <sys/vnode.h> + +static l_close_t snplclose; +static l_write_t snplwrite; +static d_open_t snpopen; +static d_close_t snpclose; +static d_read_t snpread; +static d_write_t snpwrite; +static d_ioctl_t snpioctl; +static d_poll_t snppoll; + +#define CDEV_MAJOR 53 +static struct cdevsw snp_cdevsw = { + /* open */ snpopen, + /* close */ snpclose, + /* read */ snpread, + /* write */ snpwrite, + /* ioctl */ snpioctl, + /* poll */ snppoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "snp", + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +static struct linesw snpdisc = { + ttyopen, snplclose, ttread, snplwrite, + l_nullioctl, ttyinput, ttstart, ttymodem +}; + +/* + * This is the main snoop per-device structure. + */ +struct snoop { + LIST_ENTRY(snoop) snp_list; /* List glue. */ + dev_t snp_target; /* Target tty device. */ + struct tty *snp_tty; /* Target tty pointer. */ + u_long snp_len; /* Possible length. */ + u_long snp_base; /* Data base. */ + u_long snp_blen; /* Used length. */ + caddr_t snp_buf; /* Allocation pointer. */ + int snp_flags; /* Flags. */ + struct selinfo snp_sel; /* Select info. */ + int snp_olddisc; /* Old line discipline. */ +}; + +/* + * Possible flags. + */ +#define SNOOP_ASYNC 0x0002 +#define SNOOP_OPEN 0x0004 +#define SNOOP_RWAIT 0x0008 +#define SNOOP_OFLOW 0x0010 +#define SNOOP_DOWN 0x0020 + +/* + * Other constants. + */ +#define SNOOP_MINLEN (4*1024) /* This should be power of 2. + * 4K tested to be the minimum + * for which on normal tty + * usage there is no need to + * allocate more. + */ +#define SNOOP_MAXLEN (64*1024) /* This one also,64K enough + * If we grow more,something + * really bad in this world.. + */ + +static MALLOC_DEFINE(M_SNP, "snp", "Snoop device data"); +/* + * The number of the "snoop" line discipline. This gets determined at + * module load time. + */ +static int snooplinedisc; +static udev_t snpbasedev = NOUDEV; + + +static LIST_HEAD(, snoop) snp_sclist = LIST_HEAD_INITIALIZER(&snp_sclist); + +static struct tty *snpdevtotty __P((dev_t dev)); +static void snp_clone __P((void *arg, char *name, + int namelen, dev_t *dev)); +static int snp_detach __P((struct snoop *snp)); +static int snp_down __P((struct snoop *snp)); +static int snp_in __P((struct snoop *snp, char *buf, int n)); +static int snp_modevent __P((module_t mod, int what, void *arg)); + +static int +snplclose(tp, flag) + struct tty *tp; + int flag; +{ + struct snoop *snp; + int error; + + snp = tp->t_sc; + error = snp_down(snp); + if (error != 0) + return (error); + error = ttylclose(tp, flag); + return (error); +} + +static int +snplwrite(tp, uio, flag) + struct tty *tp; + struct uio *uio; + int flag; +{ + struct iovec iov; + struct uio uio2; + struct snoop *snp; + int error, ilen; + char *ibuf; + + error = 0; + ibuf = NULL; + snp = tp->t_sc; + while (uio->uio_resid > 0) { + ilen = imin(512, uio->uio_resid); + ibuf = malloc(ilen, M_SNP, M_WAITOK); + error = uiomove(ibuf, ilen, uio); + if (error != 0) + break; + snp_in(snp, ibuf, ilen); + /* Hackish, but probably the least of all evils. */ + iov.iov_base = ibuf; + iov.iov_len = ilen; + uio2.uio_iov = &iov; + uio2.uio_iovcnt = 1; + uio2.uio_offset = 0; + uio2.uio_resid = ilen; + uio2.uio_segflg = UIO_SYSSPACE; + uio2.uio_rw = UIO_WRITE; + uio2.uio_td = uio->uio_td; + error = ttwrite(tp, &uio2, flag); + if (error != 0) + break; + free(ibuf, M_SNP); + ibuf = NULL; + } + if (ibuf != NULL) + free(ibuf, M_SNP); + return (error); +} + +static struct tty * +snpdevtotty(dev) + dev_t dev; +{ + struct cdevsw *cdp; + + cdp = devsw(dev); + if (cdp == NULL || (cdp->d_flags & D_TTY) == 0) + return (NULL); + return (dev->si_tty); +} + +#define SNP_INPUT_BUF 5 /* This is even too much, the maximal + * interactive mode write is 3 bytes + * length for function keys... + */ + +static int +snpwrite(dev, uio, flag) + dev_t dev; + struct uio *uio; + int flag; +{ + struct snoop *snp; + struct tty *tp; + int error, i, len; + char c[SNP_INPUT_BUF]; + + snp = dev->si_drv1; + tp = snp->snp_tty; + if (tp == NULL) + return (EIO); + if ((tp->t_sc == snp) && (tp->t_state & TS_SNOOP) && + tp->t_line == snooplinedisc) + goto tty_input; + + printf("Snoop: attempt to write to bad tty.\n"); + return (EIO); + +tty_input: + if (!(tp->t_state & TS_ISOPEN)) + return (EIO); + + while (uio->uio_resid > 0) { + len = imin(uio->uio_resid, SNP_INPUT_BUF); + if ((error = uiomove(c, len, uio)) != 0) + return (error); + for (i=0; i < len; i++) { + if (ttyinput(c[i], tp)) + return (EIO); + } + } + return (0); +} + + +static int +snpread(dev, uio, flag) + dev_t dev; + struct uio *uio; + int flag; +{ + struct snoop *snp; + int error, len, n, nblen, s; + caddr_t from; + char *nbuf; + + snp = dev->si_drv1; + KASSERT(snp->snp_len + snp->snp_base <= snp->snp_blen, + ("snoop buffer error")); + + if (snp->snp_tty == NULL) + return (EIO); + + snp->snp_flags &= ~SNOOP_RWAIT; + + do { + if (snp->snp_len == 0) { + if (flag & IO_NDELAY) + return (EWOULDBLOCK); + snp->snp_flags |= SNOOP_RWAIT; + error = tsleep((caddr_t)snp, (PZERO + 1) | PCATCH, + "snprd", 0); + if (error != 0) + return (error); + } + } while (snp->snp_len == 0); + + n = snp->snp_len; + + error = 0; + while (snp->snp_len > 0 && uio->uio_resid > 0 && error == 0) { + len = min((unsigned)uio->uio_resid, snp->snp_len); + from = (caddr_t)(snp->snp_buf + snp->snp_base); + if (len == 0) + break; + + error = uiomove(from, len, uio); + snp->snp_base += len; + snp->snp_len -= len; + } + if ((snp->snp_flags & SNOOP_OFLOW) && (n < snp->snp_len)) { + snp->snp_flags &= ~SNOOP_OFLOW; + } + s = spltty(); + nblen = snp->snp_blen; + if (((nblen / 2) >= SNOOP_MINLEN) && (nblen / 2) >= snp->snp_len) { + while (nblen / 2 >= snp->snp_len && nblen / 2 >= SNOOP_MINLEN) + nblen = nblen / 2; + if ((nbuf = malloc(nblen, M_SNP, M_NOWAIT)) != NULL) { + bcopy(snp->snp_buf + snp->snp_base, nbuf, snp->snp_len); + free(snp->snp_buf, M_SNP); + snp->snp_buf = nbuf; + snp->snp_blen = nblen; + snp->snp_base = 0; + } + } + splx(s); + + return (error); +} + +static int +snp_in(snp, buf, n) + struct snoop *snp; + char *buf; + int n; +{ + int s_free, s_tail; + int s, len, nblen; + caddr_t from, to; + char *nbuf; + + KASSERT(n >= 0, ("negative snoop char count")); + + if (n == 0) + return (0); + + if (snp->snp_flags & SNOOP_DOWN) { + printf("Snoop: more data to down interface.\n"); + return (0); + } + + if (snp->snp_flags & SNOOP_OFLOW) { + printf("Snoop: buffer overflow.\n"); + /* + * On overflow we just repeat the standart close + * procedure...yes , this is waste of space but.. Then next + * read from device will fail if one would recall he is + * snooping and retry... + */ + + return (snp_down(snp)); + } + s_tail = snp->snp_blen - (snp->snp_len + snp->snp_base); + s_free = snp->snp_blen - snp->snp_len; + + + if (n > s_free) { + s = spltty(); + nblen = snp->snp_blen; + while ((n > s_free) && ((nblen * 2) <= SNOOP_MAXLEN)) { + nblen = snp->snp_blen * 2; + s_free = nblen - (snp->snp_len + snp->snp_base); + } + if ((n <= s_free) && (nbuf = malloc(nblen, M_SNP, M_NOWAIT))) { + bcopy(snp->snp_buf + snp->snp_base, nbuf, snp->snp_len); + free(snp->snp_buf, M_SNP); + snp->snp_buf = nbuf; + snp->snp_blen = nblen; + snp->snp_base = 0; + } else { + snp->snp_flags |= SNOOP_OFLOW; + if (snp->snp_flags & SNOOP_RWAIT) { + snp->snp_flags &= ~SNOOP_RWAIT; + wakeup((caddr_t)snp); + } + splx(s); + return (0); + } + splx(s); + } + if (n > s_tail) { + from = (caddr_t)(snp->snp_buf + snp->snp_base); + to = (caddr_t)(snp->snp_buf); + len = snp->snp_len; + bcopy(from, to, len); + snp->snp_base = 0; + } + to = (caddr_t)(snp->snp_buf + snp->snp_base + snp->snp_len); + bcopy(buf, to, n); + snp->snp_len += n; + + if (snp->snp_flags & SNOOP_RWAIT) { + snp->snp_flags &= ~SNOOP_RWAIT; + wakeup((caddr_t)snp); + } + selwakeup(&snp->snp_sel); + snp->snp_sel.si_pid = 0; + + return (n); +} + +static int +snpopen(dev, flag, mode, td) + dev_t dev; + int flag, mode; + struct thread *td; +{ + struct snoop *snp; + + if (dev->si_drv1 == NULL) { + if (!(dev->si_flags & SI_NAMED)) + make_dev(&snp_cdevsw, minor(dev), UID_ROOT, GID_WHEEL, + 0600, "snp%d", dev2unit(dev)); + dev->si_drv1 = snp = malloc(sizeof(*snp), M_SNP, + M_WAITOK | M_ZERO); + } else + return (EBUSY); + + /* + * We intentionally do not OR flags with SNOOP_OPEN, but set them so + * all previous settings (especially SNOOP_OFLOW) will be cleared. + */ + snp->snp_flags = SNOOP_OPEN; + + snp->snp_buf = malloc(SNOOP_MINLEN, M_SNP, M_WAITOK); + snp->snp_blen = SNOOP_MINLEN; + snp->snp_base = 0; + snp->snp_len = 0; + + /* + * snp_tty == NULL is for inactive snoop devices. + */ + snp->snp_tty = NULL; + snp->snp_target = NODEV; + + LIST_INSERT_HEAD(&snp_sclist, snp, snp_list); + return (0); +} + + +static int +snp_detach(snp) + struct snoop *snp; +{ + struct tty *tp; + + snp->snp_base = 0; + snp->snp_len = 0; + + /* + * If line disc. changed we do not touch this pointer, SLIP/PPP will + * change it anyway. + */ + tp = snp->snp_tty; + if (tp == NULL) + goto detach_notty; + + if (tp && (tp->t_sc == snp) && (tp->t_state & TS_SNOOP) && + tp->t_line == snooplinedisc) { + tp->t_sc = NULL; + tp->t_state &= ~TS_SNOOP; + tp->t_line = snp->snp_olddisc; + } else + printf("Snoop: bad attached tty data.\n"); + + snp->snp_tty = NULL; + snp->snp_target = NODEV; + +detach_notty: + selwakeup(&snp->snp_sel); + snp->snp_sel.si_pid = 0; + if ((snp->snp_flags & SNOOP_OPEN) == 0) + free(snp, M_SNP); + + return (0); +} + +static int +snpclose(dev, flags, fmt, td) + dev_t dev; + int flags; + int fmt; + struct thread *td; +{ + struct snoop *snp; + + snp = dev->si_drv1; + snp->snp_blen = 0; + LIST_REMOVE(snp, snp_list); + free(snp->snp_buf, M_SNP); + snp->snp_flags &= ~SNOOP_OPEN; + dev->si_drv1 = NULL; + + return (snp_detach(snp)); +} + +static int +snp_down(snp) + struct snoop *snp; +{ + + if (snp->snp_blen != SNOOP_MINLEN) { + free(snp->snp_buf, M_SNP); + snp->snp_buf = malloc(SNOOP_MINLEN, M_SNP, M_WAITOK); + snp->snp_blen = SNOOP_MINLEN; + } + snp->snp_flags |= SNOOP_DOWN; + + return (snp_detach(snp)); +} + +static int +snpioctl(dev, cmd, data, flags, td) + dev_t dev; + u_long cmd; + caddr_t data; + int flags; + struct thread *td; +{ + struct snoop *snp; + struct tty *tp, *tpo; + dev_t tdev; + int s; + + snp = dev->si_drv1; + switch (cmd) { + case SNPSTTY: + tdev = udev2dev(*((udev_t *)data), 0); + if (tdev == NODEV) + return (snp_down(snp)); + + tp = snpdevtotty(tdev); + if (!tp) + return (EINVAL); + if (tp->t_state & TS_SNOOP) + return (EBUSY); + + s = spltty(); + + if (snp->snp_target == NODEV) { + tpo = snp->snp_tty; + if (tpo) + tpo->t_state &= ~TS_SNOOP; + } + + tp->t_sc = (caddr_t)snp; + tp->t_state |= TS_SNOOP; + snp->snp_olddisc = tp->t_line; + tp->t_line = snooplinedisc; + snp->snp_tty = tp; + snp->snp_target = tdev; + + /* + * Clean overflow and down flags - + * we'll have a chance to get them in the future :))) + */ + snp->snp_flags &= ~SNOOP_OFLOW; + snp->snp_flags &= ~SNOOP_DOWN; + splx(s); + break; + + case SNPGTTY: + /* + * We keep snp_target field specially to make + * SNPGTTY happy, else we can't know what is device + * major/minor for tty. + */ + *((dev_t *)data) = snp->snp_target; + break; + + case FIONBIO: + break; + + case FIOASYNC: + if (*(int *)data) + snp->snp_flags |= SNOOP_ASYNC; + else + snp->snp_flags &= ~SNOOP_ASYNC; + break; + + case FIONREAD: + s = spltty(); + if (snp->snp_tty != NULL) + *(int *)data = snp->snp_len; + else + if (snp->snp_flags & SNOOP_DOWN) { + if (snp->snp_flags & SNOOP_OFLOW) + *(int *)data = SNP_OFLOW; + else + *(int *)data = SNP_TTYCLOSE; + } else { + *(int *)data = SNP_DETACH; + } + splx(s); + break; + + default: + return (ENOTTY); + } + return (0); +} + +static int +snppoll(dev, events, td) + dev_t dev; + int events; + struct thread *td; +{ + struct snoop *snp; + int revents; + + snp = dev->si_drv1; + revents = 0; + /* + * If snoop is down, we don't want to poll() forever so we return 1. + * Caller should see if we down via FIONREAD ioctl(). The last should + * return -1 to indicate down state. + */ + if (events & (POLLIN | POLLRDNORM)) { + if (snp->snp_flags & SNOOP_DOWN || snp->snp_len > 0) + revents |= events & (POLLIN | POLLRDNORM); + else + selrecord(td, &snp->snp_sel); + } + return (revents); +} + +static void +snp_clone(arg, name, namelen, dev) + void *arg; + char *name; + int namelen; + dev_t *dev; +{ + int u; + + if (*dev != NODEV) + return; + if (dev_stdclone(name, NULL, "snp", &u) != 1) + return; + *dev = make_dev(&snp_cdevsw, unit2minor(u), UID_ROOT, GID_WHEEL, 0600, + "snp%d", u); + if (snpbasedev == NOUDEV) + snpbasedev = (*dev)->si_udev; + else { + (*dev)->si_flags |= SI_CHEAPCLONE; + dev_depends(udev2dev(snpbasedev, 0), *dev); + } +} + +static int +snp_modevent(mod, type, data) + module_t mod; + int type; + void *data; +{ + static eventhandler_tag eh_tag; + + switch (type) { + case MOD_LOAD: + /* XXX error checking. */ + eh_tag = EVENTHANDLER_REGISTER(dev_clone, snp_clone, 0, 1000); + snooplinedisc = ldisc_register(LDISC_LOAD, &snpdisc); + cdevsw_add(&snp_cdevsw); + break; + case MOD_UNLOAD: + if (!LIST_EMPTY(&snp_sclist)) + return (EBUSY); + EVENTHANDLER_DEREGISTER(dev_clone, eh_tag); + if (snpbasedev != NOUDEV) + destroy_dev(udev2dev(snpbasedev, 0)); + ldisc_deregister(snooplinedisc); + cdevsw_remove(&snp_cdevsw); + break; + default: + break; + } + return (0); +} + +static moduledata_t snp_mod = { + "snp", + snp_modevent, + NULL +}; +DECLARE_MODULE(snp, snp_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE + CDEV_MAJOR); |
