]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/port/devloopback.c
fix fuckup
[plan9front.git] / sys / src / 9 / port / devloopback.c
1 #include        "u.h"
2 #include        "../port/lib.h"
3 #include        "mem.h"
4 #include        "dat.h"
5 #include        "fns.h"
6 #include        "../port/error.h"
7
8 typedef struct Link     Link;
9 typedef struct Loop     Loop;
10
11 struct Link
12 {
13         Lock;
14
15         int     ref;
16
17         long    packets;        /* total number of packets sent */
18         long    bytes;          /* total number of bytes sent */
19         int     indrop;         /* enable dropping on iq overflow */
20         long    soverflows;     /* packets dropped because iq overflowed */
21         long    droprate;       /* drop 1/droprate packets in tq */
22         long    drops;          /* packets deliberately dropped */
23
24         vlong   delay0ns;       /* nanosec of delay in the link */
25         long    delaynns;       /* nanosec of delay per byte */
26
27         Block   *tq;            /* transmission queue */
28         Block   *tqtail;
29         vlong   tout;           /* time the last packet in tq is really out */
30         vlong   tin;            /* time the head packet in tq enters the remote side  */
31
32         long    limit;          /* queue buffering limit */
33         Queue   *oq;            /* output queue from other side & packets in the link */
34         Queue   *iq;
35
36         Timer   ci;             /* time to move packets from  next packet from oq */
37 };
38
39 struct Loop
40 {
41         QLock;
42         int     ref;
43         int     minmtu;         /* smallest block transmittable */
44         Loop    *next;
45         ulong   path;
46         Link    link[2];
47 };
48
49 static struct
50 {
51         Lock;
52         ulong   path;
53 } loopbackalloc;
54
55 enum
56 {
57         Qtopdir=        1,              /* top level directory */
58
59         Qloopdir,                       /* loopback* directory */
60
61         Qportdir,                       /* directory each end of the loop */
62         Qctl,
63         Qstatus,
64         Qstats,
65         Qdata,
66
67         MaxQ,
68
69         Nloopbacks      = 5,
70
71         Statelen        = 23*1024,      /* status buffer size */
72
73         Tmsize          = 8,
74         Delayn          = 10000,        /* default delays in ns */
75         Delay0          = 2500000,
76
77         Loopqlim        = 32*1024,      /* default size of queues */
78 };
79
80 static Dirtab loopportdir[] =
81 {
82         "ctl",          {Qctl},         0,                      0222,
83         "status",       {Qstatus},      0,                      0444,
84         "stats",        {Qstats},       0,                      0444,
85         "data",         {Qdata},        0,                      0666,
86 };
87 static Dirtab loopdirs[MaxQ];
88
89 static Loop     loopbacks[Nloopbacks];
90
91 #define TYPE(x)         (((ulong)(x))&0xff)
92 #define ID(x)           (((ulong)(x))>>8)
93 #define QID(x,y)        ((((ulong)(x))<<8)|((ulong)(y)))
94
95 static void     looper(Loop *lb);
96 static long     loopoput(Loop *lb, Link *link, Block *bp);
97 static void     ptime(uchar *p, vlong t);
98 static vlong    gtime(uchar *p);
99 static void     closelink(Link *link, int dofree);
100 static void     pushlink(Link *link, vlong now);
101 static void     freelb(Loop *lb);
102 static void     linkintr(Ureg*, Timer *ci);
103
104 static void
105 loopbackinit(void)
106 {
107         int i;
108
109         for(i = 0; i < Nloopbacks; i++)
110                 loopbacks[i].path = i;
111
112         /* invert directory tables for non-directory entries */
113         for(i=0; i<nelem(loopportdir); i++)
114                 loopdirs[loopportdir[i].qid.path] = loopportdir[i];
115 }
116
117 static Chan*
118 loopbackattach(char *spec)
119 {
120         Loop *volatile lb;
121         Queue *q;
122         Chan *c;
123         int chan;
124         int dev;
125
126         dev = 0;
127         if(spec != nil){
128                 dev = atoi(spec);
129                 if(dev >= Nloopbacks)
130                         error(Ebadspec);
131         }
132
133         c = devattach('X', spec);
134         if(waserror()){
135                 chanfree(c);
136                 nexterror();
137         }
138
139         lb = &loopbacks[dev];
140         qlock(lb);
141         if(waserror()){
142                 lb->ref--;
143                 qunlock(lb);
144                 nexterror();
145         }
146
147         lb->ref++;
148         if(lb->ref == 1){
149                 for(chan = 0; chan < 2; chan++){
150                         lb->link[chan].ci.mode = Trelative;
151                         lb->link[chan].ci.a = &lb->link[chan];
152                         lb->link[chan].ci.f = linkintr;
153                         lb->link[chan].limit = Loopqlim;
154                         q = qopen(lb->link[chan].limit, 0, 0, 0);
155                         lb->link[chan].iq = q;
156                         if(q == nil){
157                                 freelb(lb);
158                                 exhausted("memory");
159                         }
160                         q = qopen(lb->link[chan].limit, 0, 0, 0);
161                         lb->link[chan].oq = q;
162                         if(q == nil){
163                                 freelb(lb);
164                                 exhausted("memory");
165                         }
166                         lb->link[chan].indrop = 1;
167
168                         lb->link[chan].delaynns = Delayn;
169                         lb->link[chan].delay0ns = Delay0;
170                 }
171         }
172         poperror();
173         qunlock(lb);
174
175         poperror();
176
177         mkqid(&c->qid, QID(0, Qtopdir), 0, QTDIR);
178         c->aux = lb;
179         c->dev = dev;
180         return c;
181 }
182
183 static int
184 loopbackgen(Chan *c, char*, Dirtab*, int, int i, Dir *dp)
185 {
186         Dirtab *tab;
187         int len, type;
188         Qid qid;
189
190         type = TYPE(c->qid.path);
191         if(i == DEVDOTDOT){
192                 switch(type){
193                 case Qtopdir:
194                 case Qloopdir:
195                         snprint(up->genbuf, sizeof(up->genbuf), "#X%ld", c->dev);
196                         mkqid(&qid, QID(0, Qtopdir), 0, QTDIR);
197                         devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
198                         break;
199                 case Qportdir:
200                         snprint(up->genbuf, sizeof(up->genbuf), "loopback%ld", c->dev);
201                         mkqid(&qid, QID(0, Qloopdir), 0, QTDIR);
202                         devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
203                         break;
204                 default:
205                         panic("loopbackgen %llux", c->qid.path);
206                 }
207                 return 1;
208         }
209
210         switch(type){
211         case Qtopdir:
212                 if(i != 0)
213                         return -1;
214                 snprint(up->genbuf, sizeof(up->genbuf), "loopback%ld", c->dev);
215                 mkqid(&qid, QID(0, Qloopdir), 0, QTDIR);
216                 devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
217                 return 1;
218         case Qloopdir:
219                 if(i >= 2)
220                         return -1;
221                 snprint(up->genbuf, sizeof(up->genbuf), "%d", i);
222                 mkqid(&qid, QID(i, QID(0, Qportdir)), 0, QTDIR);
223                 devdir(c, qid, up->genbuf, 0, eve, 0555, dp);
224                 return 1;
225         case Qportdir:
226                 if(i >= nelem(loopportdir))
227                         return -1;
228                 tab = &loopportdir[i];
229                 mkqid(&qid, QID(ID(c->qid.path), tab->qid.path), 0, QTFILE);
230                 devdir(c, qid, tab->name, tab->length, eve, tab->perm, dp);
231                 return 1;
232         default:
233                 /* non directory entries end up here; must be in lowest level */
234                 if(c->qid.type & QTDIR)
235                         panic("loopbackgen: unexpected directory");     
236                 if(i != 0)
237                         return -1;
238                 tab = &loopdirs[type];
239                 if(tab == nil)
240                         panic("loopbackgen: unknown type: %d", type);
241                 len = tab->length;
242                 devdir(c, c->qid, tab->name, len, eve, tab->perm, dp);
243                 return 1;
244         }
245 }
246
247
248 static Walkqid*
249 loopbackwalk(Chan *c, Chan *nc, char **name, int nname)
250 {
251         Walkqid *wq;
252         Loop *lb;
253
254         wq = devwalk(c, nc, name, nname, nil, 0, loopbackgen);
255         if(wq != nil && wq->clone != nil && wq->clone != c){
256                 lb = c->aux;
257                 qlock(lb);
258                 lb->ref++;
259                 if((c->flag & COPEN) && TYPE(c->qid.path) == Qdata)
260                         lb->link[ID(c->qid.path)].ref++;
261                 qunlock(lb);
262         }
263         return wq;
264 }
265
266 static int
267 loopbackstat(Chan *c, uchar *db, int n)
268 {
269         return devstat(c, db, n, nil, 0, loopbackgen);
270 }
271
272 /*
273  *  if the stream doesn't exist, create it
274  */
275 static Chan*
276 loopbackopen(Chan *c, int omode)
277 {
278         Loop *lb;
279
280         if(c->qid.type & QTDIR){
281                 if(omode != OREAD)
282                         error(Ebadarg);
283                 c->mode = omode;
284                 c->flag |= COPEN;
285                 c->offset = 0;
286                 return c;
287         }
288
289         lb = c->aux;
290         qlock(lb);
291         if(TYPE(c->qid.path) == Qdata){
292                 if(lb->link[ID(c->qid.path)].ref){
293                         qunlock(lb);
294                         error(Einuse);
295                 }
296                 lb->link[ID(c->qid.path)].ref++;
297         }
298         qunlock(lb);
299
300         c->mode = openmode(omode);
301         c->flag |= COPEN;
302         c->offset = 0;
303         c->iounit = qiomaxatomic;
304         return c;
305 }
306
307 static void
308 loopbackclose(Chan *c)
309 {
310         Loop *lb;
311         int ref, chan;
312
313         lb = c->aux;
314
315         qlock(lb);
316
317         /*
318          * closing either side hangs up the stream
319          */
320         if((c->flag & COPEN) && TYPE(c->qid.path) == Qdata){
321                 chan = ID(c->qid.path);
322                 if(--lb->link[chan].ref == 0){
323                         qhangup(lb->link[chan ^ 1].oq, nil);
324                         looper(lb);
325                 }
326         }
327
328
329         /*
330          *  if both sides are closed, they are reusable
331          */
332         if(lb->link[0].ref == 0 && lb->link[1].ref == 0){
333                 for(chan = 0; chan < 2; chan++){
334                         closelink(&lb->link[chan], 0);
335                         qreopen(lb->link[chan].iq);
336                         qreopen(lb->link[chan].oq);
337                         qsetlimit(lb->link[chan].oq, lb->link[chan].limit);
338                         qsetlimit(lb->link[chan].iq, lb->link[chan].limit);
339                 }
340         }
341         ref = --lb->ref;
342         if(ref == 0)
343                 freelb(lb);
344         qunlock(lb);
345 }
346
347 static void
348 freelb(Loop *lb)
349 {
350         int chan;
351
352         for(chan = 0; chan < 2; chan++)
353                 closelink(&lb->link[chan], 1);
354 }
355
356 /*
357  * called with the Loop qlocked,
358  * so only pushlink can mess with the queues
359  */
360 static void
361 closelink(Link *link, int dofree)
362 {
363         Queue *iq, *oq;
364         Block *bp;
365
366         ilock(link);
367         iq = link->iq;
368         oq = link->oq;
369         bp = link->tq;
370         link->tq = nil;
371         link->tqtail = nil;
372         link->tout = 0;
373         link->tin = 0;
374         timerdel(&link->ci);
375         iunlock(link);
376         if(iq != nil){
377                 qclose(iq);
378                 if(dofree){
379                         ilock(link);
380                         free(iq);
381                         link->iq = nil;
382                         iunlock(link);
383                 }
384         }
385         if(oq != nil){
386                 qclose(oq);
387                 if(dofree){
388                         ilock(link);
389                         free(oq);
390                         link->oq = nil;
391                         iunlock(link);
392                 }
393         }
394         freeblist(bp);
395 }
396
397 static long
398 loopbackread(Chan *c, void *va, long n, vlong offset)
399 {
400         Loop *lb;
401         Link *link;
402         char *buf;
403         long rv;
404
405         lb = c->aux;
406         switch(TYPE(c->qid.path)){
407         default:
408                 error(Eperm);
409                 return -1;      /* not reached */
410         case Qtopdir:
411         case Qloopdir:
412         case Qportdir:
413                 return devdirread(c, va, n, nil, 0, loopbackgen);
414         case Qdata:
415                 return qread(lb->link[ID(c->qid.path)].iq, va, n);
416         case Qstatus:
417                 link = &lb->link[ID(c->qid.path)];
418                 buf = smalloc(Statelen);
419                 rv = snprint(buf, Statelen, "delay %lld %ld\n", link->delay0ns, link->delaynns);
420                 rv += snprint(buf+rv, Statelen-rv, "limit %ld\n", link->limit);
421                 rv += snprint(buf+rv, Statelen-rv, "indrop %d\n", link->indrop);
422                 snprint(buf+rv, Statelen-rv, "droprate %ld\n", link->droprate);
423                 rv = readstr(offset, va, n, buf);
424                 free(buf);
425                 break;
426         case Qstats:
427                 link = &lb->link[ID(c->qid.path)];
428                 buf = smalloc(Statelen);
429                 rv = snprint(buf, Statelen, "packets: %ld\n", link->packets);
430                 rv += snprint(buf+rv, Statelen-rv, "bytes: %ld\n", link->bytes);
431                 rv += snprint(buf+rv, Statelen-rv, "dropped: %ld\n", link->drops);
432                 snprint(buf+rv, Statelen-rv, "soft overflows: %ld\n", link->soverflows);
433                 rv = readstr(offset, va, n, buf);
434                 free(buf);
435                 break;
436         }
437         return rv;
438 }
439
440 static Block*
441 loopbackbread(Chan *c, long n, ulong offset)
442 {
443         Loop *lb;
444
445         lb = c->aux;
446         if(TYPE(c->qid.path) == Qdata)
447                 return qbread(lb->link[ID(c->qid.path)].iq, n);
448
449         return devbread(c, n, offset);
450 }
451
452 static long
453 loopbackbwrite(Chan *c, Block *bp, ulong off)
454 {
455         Loop *lb;
456
457         lb = c->aux;
458         if(TYPE(c->qid.path) == Qdata)
459                 return loopoput(lb, &lb->link[ID(c->qid.path) ^ 1], bp);
460         return devbwrite(c, bp, off);
461 }
462
463 static long
464 loopbackwrite(Chan *c, void *va, long n, vlong off)
465 {
466         Loop *lb;
467         Link *link;
468         Cmdbuf *volatile cb;
469         Block *volatile bp;
470         vlong d0, d0ns;
471         long dn, dnns;
472
473         switch(TYPE(c->qid.path)){
474         case Qdata:
475                 bp = allocb(n);
476                 if(waserror()){
477                         freeb(bp);
478                         nexterror();
479                 }
480                 memmove(bp->wp, va, n);
481                 poperror();
482                 bp->wp += n;
483                 return loopbackbwrite(c, bp, off);
484         case Qctl:
485                 lb = c->aux;
486                 link = &lb->link[ID(c->qid.path)];
487                 cb = parsecmd(va, n);
488                 if(waserror()){
489                         free(cb);
490                         nexterror();
491                 }
492                 if(cb->nf < 1)
493                         error("short control request");
494                 if(strcmp(cb->f[0], "delay") == 0){
495                         if(cb->nf != 3)
496                                 error("usage: delay latency bytedelay");
497                         d0ns = strtoll(cb->f[1], nil, 10);
498                         dnns = strtol(cb->f[2], nil, 10);
499
500                         /*
501                          * it takes about 20000 cycles on a pentium ii
502                          * to run pushlink; perhaps this should be accounted.
503                          */
504
505                         ilock(link);
506                         link->delay0ns = d0ns;
507                         link->delaynns = dnns;
508                         iunlock(link);
509                 }else if(strcmp(cb->f[0], "indrop") == 0){
510                         if(cb->nf != 2)
511                                 error("usage: indrop [01]");
512                         ilock(link);
513                         link->indrop = strtol(cb->f[1], nil, 0) != 0;
514                         iunlock(link);
515                 }else if(strcmp(cb->f[0], "droprate") == 0){
516                         if(cb->nf != 2)
517                                 error("usage: droprate ofn");
518                         ilock(link);
519                         link->droprate = strtol(cb->f[1], nil, 0);
520                         iunlock(link);
521                 }else if(strcmp(cb->f[0], "limit") == 0){
522                         if(cb->nf != 2)
523                                 error("usage: limit maxqsize");
524                         ilock(link);
525                         link->limit = strtol(cb->f[1], nil, 0);
526                         qsetlimit(link->oq, link->limit);
527                         qsetlimit(link->iq, link->limit);
528                         iunlock(link);
529                 }else if(strcmp(cb->f[0], "reset") == 0){
530                         if(cb->nf != 1)
531                                 error("usage: reset");
532                         ilock(link);
533                         link->packets = 0;
534                         link->bytes = 0;
535                         link->indrop = 0;
536                         link->soverflows = 0;
537                         link->drops = 0;
538                         iunlock(link);
539                 }else
540                         error("unknown control request");
541                 poperror();
542                 free(cb);
543                 break;
544         default:
545                 error(Eperm);
546         }
547
548         return n;
549 }
550
551 static long
552 loopoput(Loop *lb, Link *link, Block *volatile bp)
553 {
554         long n;
555
556         n = BLEN(bp);
557
558         /* make it a single block with space for the loopback timing header */
559         if(waserror()){
560                 freeb(bp);
561                 nexterror();
562         }
563         bp = padblock(bp, Tmsize);
564         if(bp->next)
565                 bp = concatblock(bp);
566         if(BLEN(bp) < lb->minmtu)
567                 bp = adjustblock(bp, lb->minmtu);
568         poperror();
569         ptime(bp->rp, todget(nil));
570
571         link->packets++;
572         link->bytes += n;
573
574         qbwrite(link->oq, bp);
575
576         looper(lb);
577         return n;
578 }
579
580 static void
581 looper(Loop *lb)
582 {
583         vlong t;
584         int chan;
585
586         t = todget(nil);
587         for(chan = 0; chan < 2; chan++)
588                 pushlink(&lb->link[chan], t);
589 }
590
591 static void
592 linkintr(Ureg*, Timer *ci)
593 {
594         Link *link;
595
596         link = ci->a;
597         pushlink(link, ci->ns);
598 }
599
600 /*
601  * move blocks between queues if they are ready.
602  * schedule an interrupt for the next interesting time.
603  *
604  * must be called with the link ilocked.
605  */
606 static void
607 pushlink(Link *link, vlong now)
608 {
609         Block *bp;
610         vlong tout, tin;
611
612         /*
613          * put another block in the link queue
614          */
615         ilock(link);
616         if(link->iq == nil || link->oq == nil){
617                 iunlock(link);
618                 return;
619
620         }
621         timerdel(&link->ci);
622
623         /*
624          * put more blocks into the xmit queue
625          * use the time the last packet was supposed to go out
626          * as the start time for the next packet, rather than
627          * the current time.  this more closely models a network
628          * device which can queue multiple output packets.
629          */
630         tout = link->tout;
631         if(!tout)
632                 tout = now;
633         while(tout <= now){
634                 bp = qget(link->oq);
635                 if(bp == nil){
636                         tout = 0;
637                         break;
638                 }
639
640                 /*
641                  * can't send the packet before it gets queued
642                  */
643                 tin = gtime(bp->rp);
644                 if(tin > tout)
645                         tout = tin;
646                 tout = tout + (BLEN(bp) - Tmsize) * link->delayn;
647
648                 /*
649                  * drop packets
650                  */
651                 if(link->droprate && nrand(link->droprate) == 0)
652                         link->drops++;
653                 else{
654                         ptime(bp->rp, tout + link->delay0ns);
655                         if(link->tq == nil)
656                                 link->tq = bp;
657                         else
658                                 link->tqtail->next = bp;
659                         link->tqtail = bp;
660                 }
661         }
662
663         /*
664          * record the next time a packet can be sent,
665          * but don't schedule an interrupt if none is waiting
666          */
667         link->tout = tout;
668         if(!qcanread(link->oq))
669                 tout = 0;
670
671         /*
672          * put more blocks into the receive queue
673          */
674         tin = 0;
675         while(bp = link->tq){
676                 tin = gtime(bp->rp);
677                 if(tin > now)
678                         break;
679                 bp->rp += Tmsize;
680                 link->tq = bp->next;
681                 bp->next = nil;
682                 if(!link->indrop)
683                         qpassnolim(link->iq, bp);
684                 else if(qpass(link->iq, bp) < 0)
685                         link->soverflows++;
686                 tin = 0;
687         }
688         if(bp == nil && qisclosed(link->oq) && !qcanread(link->oq) && !qisclosed(link->iq))
689                 qhangup(link->iq, nil);
690         link->tin = tin;
691         if(!tin || tin > tout && tout)
692                 tin = tout;
693
694         link->ci.ns = tin - now;
695         if(tin){
696                 if(tin < now)
697                         panic("loopback unfinished business");
698                 timeradd(&link->ci);
699         }
700         iunlock(link);
701 }
702
703 static void
704 ptime(uchar *p, vlong t)
705 {
706         ulong tt;
707
708         tt = t >> 32;
709         p[0] = tt >> 24;
710         p[1] = tt >> 16;
711         p[2] = tt >> 8;
712         p[3] = tt;
713         tt = t;
714         p[4] = tt >> 24;
715         p[5] = tt >> 16;
716         p[6] = tt >> 8;
717         p[7] = tt;
718 }
719
720 static vlong
721 gtime(uchar *p)
722 {
723         ulong t1, t2;
724
725         t1 = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
726         t2 = (p[4] << 24) | (p[5] << 16) | (p[6] << 8) | p[7];
727         return ((vlong)t1 << 32) | t2;
728 }
729
730 Dev loopbackdevtab = {
731         'X',
732         "loopback",
733
734         devreset,
735         loopbackinit,
736         devshutdown,
737         loopbackattach,
738         loopbackwalk,
739         loopbackstat,
740         loopbackopen,
741         devcreate,
742         loopbackclose,
743         loopbackread,
744         loopbackbread,
745         loopbackwrite,
746         loopbackbwrite,
747         devremove,
748         devwstat,
749 };