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