summaryrefslogtreecommitdiff
path: root/sys/src/9/port/devloopback.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/port/devloopback.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/port/devloopback.c')
-rwxr-xr-xsys/src/9/port/devloopback.c743
1 files changed, 743 insertions, 0 deletions
diff --git a/sys/src/9/port/devloopback.c b/sys/src/9/port/devloopback.c
new file mode 100755
index 000000000..d3a64932b
--- /dev/null
+++ b/sys/src/9/port/devloopback.c
@@ -0,0 +1,743 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+
+typedef struct Link Link;
+typedef struct Loop Loop;
+
+struct Link
+{
+ Lock;
+
+ int ref;
+
+ long packets; /* total number of packets sent */
+ long bytes; /* total number of bytes sent */
+ int indrop; /* enable dropping on iq overflow */
+ long soverflows; /* packets dropped because iq overflowed */
+ long droprate; /* drop 1/droprate packets in tq */
+ long drops; /* packets deliberately dropped */
+
+ vlong delay0ns; /* nanosec of delay in the link */
+ long delaynns; /* nanosec of delay per byte */
+
+ Block *tq; /* transmission queue */
+ Block *tqtail;
+ vlong tout; /* time the last packet in tq is really out */
+ vlong tin; /* time the head packet in tq enters the remote side */
+
+ long limit; /* queue buffering limit */
+ Queue *oq; /* output queue from other side & packets in the link */
+ Queue *iq;
+
+ Timer ci; /* time to move packets from next packet from oq */
+};
+
+struct Loop
+{
+ QLock;
+ int ref;
+ int minmtu; /* smallest block transmittable */
+ Loop *next;
+ ulong path;
+ Link link[2];
+};
+
+static struct
+{
+ Lock;
+ ulong path;
+} loopbackalloc;
+
+enum
+{
+ Qtopdir= 1, /* top level directory */
+
+ Qloopdir, /* loopback* directory */
+
+ Qportdir, /* directory each end of the loop */
+ Qctl,
+ Qstatus,
+ Qstats,
+ Qdata,
+
+ MaxQ,
+
+ Nloopbacks = 5,
+
+ Statelen = 23*1024, /* status buffer size */
+
+ Tmsize = 8,
+ Delayn = 10000, /* default delays in ns */
+ Delay0 = 2500000,
+
+ Loopqlim = 32*1024, /* default size of queues */
+};
+
+static Dirtab loopportdir[] =
+{
+ "ctl", {Qctl}, 0, 0222,
+ "status", {Qstatus}, 0, 0444,
+ "stats", {Qstats}, 0, 0444,
+ "data", {Qdata}, 0, 0666,
+};
+static Dirtab loopdirs[MaxQ];
+
+static Loop loopbacks[Nloopbacks];
+
+#define TYPE(x) (((ulong)(x))&0xff)
+#define ID(x) (((ulong)(x))>>8)
+#define QID(x,y) ((((ulong)(x))<<8)|((ulong)(y)))
+
+static void looper(Loop *lb);
+static long loopoput(Loop *lb, Link *link, Block *bp);
+static void ptime(uchar *p, vlong t);
+static vlong gtime(uchar *p);
+static void closelink(Link *link, int dofree);
+static void pushlink(Link *link, vlong now);
+static void freelb(Loop *lb);
+static void linkintr(Ureg*, Timer *ci);
+
+static void
+loopbackinit(void)
+{
+ int i;
+
+ for(i = 0; i < Nloopbacks; i++)
+ loopbacks[i].path = i;
+
+ /* invert directory tables for non-directory entries */
+ for(i=0; i<nelem(loopportdir); i++)
+ loopdirs[loopportdir[i].qid.path] = loopportdir[i];
+}
+
+static Chan*
+loopbackattach(char *spec)
+{
+ Loop *volatile lb;
+ Queue *q;
+ Chan *c;
+ int chan;
+ int dev;
+
+ dev = 0;
+ if(spec != nil){
+ dev = atoi(spec);
+ if(dev >= Nloopbacks)
+ error(Ebadspec);
+ }
+
+ c = devattach('X', spec);
+ lb = &loopbacks[dev];
+
+ qlock(lb);
+ if(waserror()){
+ lb->ref--;
+ qunlock(lb);
+ nexterror();
+ }
+
+ lb->ref++;
+ if(lb->ref == 1){
+ for(chan = 0; chan < 2; chan++){
+ lb->link[chan].ci.mode = Trelative;
+ lb->link[chan].ci.a = &lb->link[chan];
+ lb->link[chan].ci.f = linkintr;
+ lb->link[chan].limit = Loopqlim;
+ q = qopen(lb->link[chan].limit, 0, 0, 0);
+ lb->link[chan].iq = q;
+ if(q == nil){
+ freelb(lb);
+ exhausted("memory");
+ }
+ q = qopen(lb->link[chan].limit, 0, 0, 0);
+ lb->link[chan].oq = q;
+ if(q == nil){
+ freelb(lb);
+ exhausted("memory");
+ }
+ lb->link[chan].indrop = 1;
+
+ lb->link[chan].delaynns = Delayn;
+ lb->link[chan].delay0ns = Delay0;
+ }
+ }
+ poperror();
+ qunlock(lb);
+
+ mkqid(&c->qid, QID(0, Qtopdir), 0, QTDIR);
+ c->aux = lb;
+ c->dev = dev;
+ return c;
+}
+
+static int
+loopbackgen(Chan *c, char*, Dirtab*, int, int i, Dir *dp)
+{
+ Dirtab *tab;
+ int len, type;
+ Qid qid;
+
+ type = TYPE(c->qid.path);
+ if(i == DEVDOTDOT){
+ switch(type){
+ case Qtopdir:
+ case Qloopdir:
+ snprint(up->genbuf, sizeof(up->genbuf), "#X%ld", c->dev);
+ mkqid(&qid, QID(0, Qtopdir), 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ break;
+ case Qportdir:
+ snprint(up->genbuf, sizeof(up->genbuf), "loopback%ld", c->dev);
+ mkqid(&qid, QID(0, Qloopdir), 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ break;
+ default:
+ panic("loopbackgen %llux", c->qid.path);
+ }
+ return 1;
+ }
+
+ switch(type){
+ case Qtopdir:
+ if(i != 0)
+ return -1;
+ snprint(up->genbuf, sizeof(up->genbuf), "loopback%ld", c->dev);
+ mkqid(&qid, QID(0, Qloopdir), 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ case Qloopdir:
+ if(i >= 2)
+ return -1;
+ snprint(up->genbuf, sizeof(up->genbuf), "%d", i);
+ mkqid(&qid, QID(i, QID(0, Qportdir)), 0, QTDIR);
+ devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
+ return 1;
+ case Qportdir:
+ if(i >= nelem(loopportdir))
+ return -1;
+ tab = &loopportdir[i];
+ mkqid(&qid, QID(ID(c->qid.path), tab->qid.path), 0, QTFILE);
+ devdir(c, qid, tab->name, tab->length, eve, tab->perm, dp);
+ return 1;
+ default:
+ /* non directory entries end up here; must be in lowest level */
+ if(c->qid.type & QTDIR)
+ panic("loopbackgen: unexpected directory");
+ if(i != 0)
+ return -1;
+ tab = &loopdirs[type];
+ if(tab == nil)
+ panic("loopbackgen: unknown type: %d", type);
+ len = tab->length;
+ devdir(c, c->qid, tab->name, len, eve, tab->perm, dp);
+ return 1;
+ }
+}
+
+
+static Walkqid*
+loopbackwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+ Walkqid *wq;
+ Loop *lb;
+
+ wq = devwalk(c, nc, name, nname, nil, 0, loopbackgen);
+ if(wq != nil && wq->clone != nil && wq->clone != c){
+ lb = c->aux;
+ qlock(lb);
+ lb->ref++;
+ if((c->flag & COPEN) && TYPE(c->qid.path) == Qdata)
+ lb->link[ID(c->qid.path)].ref++;
+ qunlock(lb);
+ }
+ return wq;
+}
+
+static int
+loopbackstat(Chan *c, uchar *db, int n)
+{
+ return devstat(c, db, n, nil, 0, loopbackgen);
+}
+
+/*
+ * if the stream doesn't exist, create it
+ */
+static Chan*
+loopbackopen(Chan *c, int omode)
+{
+ Loop *lb;
+
+ if(c->qid.type & QTDIR){
+ if(omode != OREAD)
+ error(Ebadarg);
+ c->mode = omode;
+ c->flag |= COPEN;
+ c->offset = 0;
+ return c;
+ }
+
+ lb = c->aux;
+ qlock(lb);
+ if(TYPE(c->qid.path) == Qdata){
+ if(lb->link[ID(c->qid.path)].ref){
+ qunlock(lb);
+ error(Einuse);
+ }
+ lb->link[ID(c->qid.path)].ref++;
+ }
+ qunlock(lb);
+
+ c->mode = openmode(omode);
+ c->flag |= COPEN;
+ c->offset = 0;
+ c->iounit = qiomaxatomic;
+ return c;
+}
+
+static void
+loopbackclose(Chan *c)
+{
+ Loop *lb;
+ int ref, chan;
+
+ lb = c->aux;
+
+ qlock(lb);
+
+ /*
+ * closing either side hangs up the stream
+ */
+ if((c->flag & COPEN) && TYPE(c->qid.path) == Qdata){
+ chan = ID(c->qid.path);
+ if(--lb->link[chan].ref == 0){
+ qhangup(lb->link[chan ^ 1].oq, nil);
+ looper(lb);
+ }
+ }
+
+
+ /*
+ * if both sides are closed, they are reusable
+ */
+ if(lb->link[0].ref == 0 && lb->link[1].ref == 0){
+ for(chan = 0; chan < 2; chan++){
+ closelink(&lb->link[chan], 0);
+ qreopen(lb->link[chan].iq);
+ qreopen(lb->link[chan].oq);
+ qsetlimit(lb->link[chan].oq, lb->link[chan].limit);
+ qsetlimit(lb->link[chan].iq, lb->link[chan].limit);
+ }
+ }
+ ref = --lb->ref;
+ if(ref == 0)
+ freelb(lb);
+ qunlock(lb);
+}
+
+static void
+freelb(Loop *lb)
+{
+ int chan;
+
+ for(chan = 0; chan < 2; chan++)
+ closelink(&lb->link[chan], 1);
+}
+
+/*
+ * called with the Loop qlocked,
+ * so only pushlink can mess with the queues
+ */
+static void
+closelink(Link *link, int dofree)
+{
+ Queue *iq, *oq;
+ Block *bp;
+
+ ilock(link);
+ iq = link->iq;
+ oq = link->oq;
+ bp = link->tq;
+ link->tq = nil;
+ link->tqtail = nil;
+ link->tout = 0;
+ link->tin = 0;
+ timerdel(&link->ci);
+ iunlock(link);
+ if(iq != nil){
+ qclose(iq);
+ if(dofree){
+ ilock(link);
+ free(iq);
+ link->iq = nil;
+ iunlock(link);
+ }
+ }
+ if(oq != nil){
+ qclose(oq);
+ if(dofree){
+ ilock(link);
+ free(oq);
+ link->oq = nil;
+ iunlock(link);
+ }
+ }
+ freeblist(bp);
+}
+
+static long
+loopbackread(Chan *c, void *va, long n, vlong offset)
+{
+ Loop *lb;
+ Link *link;
+ char *buf;
+ long rv;
+
+ lb = c->aux;
+ switch(TYPE(c->qid.path)){
+ default:
+ error(Eperm);
+ return -1; /* not reached */
+ case Qtopdir:
+ case Qloopdir:
+ case Qportdir:
+ return devdirread(c, va, n, nil, 0, loopbackgen);
+ case Qdata:
+ return qread(lb->link[ID(c->qid.path)].iq, va, n);
+ case Qstatus:
+ link = &lb->link[ID(c->qid.path)];
+ buf = smalloc(Statelen);
+ rv = snprint(buf, Statelen, "delay %lld %ld\n", link->delay0ns, link->delaynns);
+ rv += snprint(buf+rv, Statelen-rv, "limit %ld\n", link->limit);
+ rv += snprint(buf+rv, Statelen-rv, "indrop %d\n", link->indrop);
+ snprint(buf+rv, Statelen-rv, "droprate %ld\n", link->droprate);
+ rv = readstr(offset, va, n, buf);
+ free(buf);
+ break;
+ case Qstats:
+ link = &lb->link[ID(c->qid.path)];
+ buf = smalloc(Statelen);
+ rv = snprint(buf, Statelen, "packets: %ld\n", link->packets);
+ rv += snprint(buf+rv, Statelen-rv, "bytes: %ld\n", link->bytes);
+ rv += snprint(buf+rv, Statelen-rv, "dropped: %ld\n", link->drops);
+ snprint(buf+rv, Statelen-rv, "soft overflows: %ld\n", link->soverflows);
+ rv = readstr(offset, va, n, buf);
+ free(buf);
+ break;
+ }
+ return rv;
+}
+
+static Block*
+loopbackbread(Chan *c, long n, ulong offset)
+{
+ Loop *lb;
+
+ lb = c->aux;
+ if(TYPE(c->qid.path) == Qdata)
+ return qbread(lb->link[ID(c->qid.path)].iq, n);
+
+ return devbread(c, n, offset);
+}
+
+static long
+loopbackbwrite(Chan *c, Block *bp, ulong off)
+{
+ Loop *lb;
+
+ lb = c->aux;
+ if(TYPE(c->qid.path) == Qdata)
+ return loopoput(lb, &lb->link[ID(c->qid.path) ^ 1], bp);
+ return devbwrite(c, bp, off);
+}
+
+static long
+loopbackwrite(Chan *c, void *va, long n, vlong off)
+{
+ Loop *lb;
+ Link *link;
+ Cmdbuf *volatile cb;
+ Block *volatile bp;
+ vlong d0, d0ns;
+ long dn, dnns;
+
+ switch(TYPE(c->qid.path)){
+ case Qdata:
+ bp = allocb(n);
+ if(waserror()){
+ freeb(bp);
+ nexterror();
+ }
+ memmove(bp->wp, va, n);
+ poperror();
+ bp->wp += n;
+ return loopbackbwrite(c, bp, off);
+ case Qctl:
+ lb = c->aux;
+ link = &lb->link[ID(c->qid.path)];
+ cb = parsecmd(va, n);
+ if(waserror()){
+ free(cb);
+ nexterror();
+ }
+ if(cb->nf < 1)
+ error("short control request");
+ if(strcmp(cb->f[0], "delay") == 0){
+ if(cb->nf != 3)
+ error("usage: delay latency bytedelay");
+ d0ns = strtoll(cb->f[1], nil, 10);
+ dnns = strtol(cb->f[2], nil, 10);
+
+ /*
+ * it takes about 20000 cycles on a pentium ii
+ * to run pushlink; perhaps this should be accounted.
+ */
+
+ ilock(link);
+ link->delay0ns = d0ns;
+ link->delaynns = dnns;
+ iunlock(link);
+ }else if(strcmp(cb->f[0], "indrop") == 0){
+ if(cb->nf != 2)
+ error("usage: indrop [01]");
+ ilock(link);
+ link->indrop = strtol(cb->f[1], nil, 0) != 0;
+ iunlock(link);
+ }else if(strcmp(cb->f[0], "droprate") == 0){
+ if(cb->nf != 2)
+ error("usage: droprate ofn");
+ ilock(link);
+ link->droprate = strtol(cb->f[1], nil, 0);
+ iunlock(link);
+ }else if(strcmp(cb->f[0], "limit") == 0){
+ if(cb->nf != 2)
+ error("usage: limit maxqsize");
+ ilock(link);
+ link->limit = strtol(cb->f[1], nil, 0);
+ qsetlimit(link->oq, link->limit);
+ qsetlimit(link->iq, link->limit);
+ iunlock(link);
+ }else if(strcmp(cb->f[0], "reset") == 0){
+ if(cb->nf != 1)
+ error("usage: reset");
+ ilock(link);
+ link->packets = 0;
+ link->bytes = 0;
+ link->indrop = 0;
+ link->soverflows = 0;
+ link->drops = 0;
+ iunlock(link);
+ }else
+ error("unknown control request");
+ poperror();
+ free(cb);
+ break;
+ default:
+ error(Eperm);
+ }
+
+ return n;
+}
+
+static long
+loopoput(Loop *lb, Link *link, Block *volatile bp)
+{
+ long n;
+
+ n = BLEN(bp);
+
+ /* make it a single block with space for the loopback timing header */
+ if(waserror()){
+ freeb(bp);
+ nexterror();
+ }
+ bp = padblock(bp, Tmsize);
+ if(bp->next)
+ bp = concatblock(bp);
+ if(BLEN(bp) < lb->minmtu)
+ bp = adjustblock(bp, lb->minmtu);
+ poperror();
+ ptime(bp->rp, todget(nil));
+
+ link->packets++;
+ link->bytes += n;
+
+ qbwrite(link->oq, bp);
+
+ looper(lb);
+ return n;
+}
+
+static void
+looper(Loop *lb)
+{
+ vlong t;
+ int chan;
+
+ t = todget(nil);
+ for(chan = 0; chan < 2; chan++)
+ pushlink(&lb->link[chan], t);
+}
+
+static void
+linkintr(Ureg*, Timer *ci)
+{
+ Link *link;
+
+ link = ci->a;
+ pushlink(link, ci->ns);
+}
+
+/*
+ * move blocks between queues if they are ready.
+ * schedule an interrupt for the next interesting time.
+ *
+ * must be called with the link ilocked.
+ */
+static void
+pushlink(Link *link, vlong now)
+{
+ Block *bp;
+ vlong tout, tin;
+
+ /*
+ * put another block in the link queue
+ */
+ ilock(link);
+ if(link->iq == nil || link->oq == nil){
+ iunlock(link);
+ return;
+
+ }
+ timerdel(&link->ci);
+
+ /*
+ * put more blocks into the xmit queue
+ * use the time the last packet was supposed to go out
+ * as the start time for the next packet, rather than
+ * the current time. this more closely models a network
+ * device which can queue multiple output packets.
+ */
+ tout = link->tout;
+ if(!tout)
+ tout = now;
+ while(tout <= now){
+ bp = qget(link->oq);
+ if(bp == nil){
+ tout = 0;
+ break;
+ }
+
+ /*
+ * can't send the packet before it gets queued
+ */
+ tin = gtime(bp->rp);
+ if(tin > tout)
+ tout = tin;
+ tout = tout + (BLEN(bp) - Tmsize) * link->delayn;
+
+ /*
+ * drop packets
+ */
+ if(link->droprate && nrand(link->droprate) == 0)
+ link->drops++;
+ else{
+ ptime(bp->rp, tout + link->delay0ns);
+ if(link->tq == nil)
+ link->tq = bp;
+ else
+ link->tqtail->next = bp;
+ link->tqtail = bp;
+ }
+ }
+
+ /*
+ * record the next time a packet can be sent,
+ * but don't schedule an interrupt if none is waiting
+ */
+ link->tout = tout;
+ if(!qcanread(link->oq))
+ tout = 0;
+
+ /*
+ * put more blocks into the receive queue
+ */
+ tin = 0;
+ while(bp = link->tq){
+ tin = gtime(bp->rp);
+ if(tin > now)
+ break;
+ bp->rp += Tmsize;
+ link->tq = bp->next;
+ bp->next = nil;
+ if(!link->indrop)
+ qpassnolim(link->iq, bp);
+ else if(qpass(link->iq, bp) < 0)
+ link->soverflows++;
+ tin = 0;
+ }
+ if(bp == nil && qisclosed(link->oq) && !qcanread(link->oq) && !qisclosed(link->iq))
+ qhangup(link->iq, nil);
+ link->tin = tin;
+ if(!tin || tin > tout && tout)
+ tin = tout;
+
+ link->ci.ns = tin - now;
+ if(tin){
+ if(tin < now)
+ panic("loopback unfinished business");
+ timeradd(&link->ci);
+ }
+ iunlock(link);
+}
+
+static void
+ptime(uchar *p, vlong t)
+{
+ ulong tt;
+
+ tt = t >> 32;
+ p[0] = tt >> 24;
+ p[1] = tt >> 16;
+ p[2] = tt >> 8;
+ p[3] = tt;
+ tt = t;
+ p[4] = tt >> 24;
+ p[5] = tt >> 16;
+ p[6] = tt >> 8;
+ p[7] = tt;
+}
+
+static vlong
+gtime(uchar *p)
+{
+ ulong t1, t2;
+
+ t1 = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+ t2 = (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
+ return ((vlong)t1 << 32) | t2;
+}
+
+Dev loopbackdevtab = {
+ 'X',
+ "loopback",
+
+ devreset,
+ loopbackinit,
+ devshutdown,
+ loopbackattach,
+ loopbackwalk,
+ loopbackstat,
+ loopbackopen,
+ devcreate,
+ loopbackclose,
+ loopbackread,
+ loopbackbread,
+ loopbackwrite,
+ loopbackbwrite,
+ devremove,
+ devwstat,
+};