xref: /inferno-os/os/port/devloopback.c (revision 9dc22068e29604f4b484e746112a9a4efe6fd57f)
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.tmode = Tabsolute;
147 			lb->link[chan].ci.ta = &lb->link[chan];
148 			lb->link[chan].ci.tf = 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 d0ns;
465 	long 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->ta;
591 	pushlink(link, ci->tns);
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->delaynns;
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.tns = tin;
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 };
744