plan 9 kernel history: overview | file list | diff list

2000/0708/port/devloopback.c (diff list | history)

port/devloopback.c on 2000/0617
2000/0617    
#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; 
 
2000/0708    
	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 */ 
2000/0617    
 
2000/0708    
	long	delay0ns;	/* nanosec of delay in the link */ 
	long	delaynns;	/* nanosec of delay per byte */ 
	long	delay0;		/* fastticks of delay */ 
	long	delayn; 
2000/0617    
 
	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  */ 
 
2000/0708    
	long	limit;		/* queue buffering limit */ 
2000/0617    
	Queue	*oq;		/* output queue from other side & packets in the link */ 
	Queue	*iq; 
2000/0708    
 
	Cycintr	ci;		/* time to move packets from  next packet from oq */ 
2000/0617    
}; 
 
struct Loop 
{ 
	QLock; 
	int	ref; 
	int	minmtu;		/* smallest block transmittable */ 
	Loop	*next; 
	ulong	path; 
	Link	link[2]; 
}; 
 
static struct 
{ 
	Lock; 
	ulong	path; 
} loopbackalloc; 
 
enum 
{ 
2000/0708    
	Qtopdir=	1,		/* top level directory */ 
 
	Qloopdir,			/* loopback* directory */ 
 
	Qportdir,			/* directory each end of the loop */ 
2000/0617    
	Qctl, 
	Qstatus, 
	Qstats, 
2000/0708    
	Qdata, 
2000/0617    
 
2000/0708    
	MaxQ, 
2000/0617    
 
2000/0708    
	Nloopbacks	= 1, 
 
	Statelen	= 23*1024,	/* status buffer size */ 
 
	Tmsize		= 8, 
	Delayn 		= 10000,	/* default delays */ 
	Delay0 		= 2500000, 
 
	Loopqlim	= 32*1024,	/* default size of queues */ 
2000/0617    
}; 
 
2000/0708    
static Dirtab loopportdir[] = 
2000/0617    
{ 
	"ctl",		{Qctl},		0,			0222, 
2000/0708    
	"status",	{Qstatus},	0,			0444, 
2000/0617    
	"stats",	{Qstats},	0,			0444, 
2000/0708    
	"data",		{Qdata},	0,			0666, 
2000/0617    
}; 
2000/0708    
static Dirtab loopdirs[MaxQ]; 
2000/0617    
 
2000/0708    
static Loop	loopbacks[Nloopbacks]; 
2000/0617    
 
2000/0708    
static uvlong	fasthz; 
 
#define TYPE(x) 	((x)&0xff) 
#define ID(x) 		(((x)&~CHDIR)>>8) 
#define QID(x,y) 	(((x)<<8)|(y)) 
 
#define NS2FASTHZ(t)	((fasthz*(t))/1000000000); 
 
2000/0617    
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); 
2000/0708    
static void	pushlink(Link *link, vlong now); 
2000/0617    
static void	freelb(Loop *lb); 
2000/0708    
static void	linkintr(Ureg*, Cycintr *ci); 
2000/0617    
 
static void 
loopbackinit(void) 
{ 
	int i; 
 
2000/0708    
	for(i = 0; i < Nloopbacks; i++) 
2000/0617    
		loopbacks[i].path = i; 
2000/0708    
 
	/* invert directory tables for non-directory entries */ 
	for(i=0; i<nelem(loopportdir); i++) 
		loopdirs[loopportdir[i].qid.path] = loopportdir[i]; 
2000/0617    
} 
 
static Chan* 
loopbackattach(char *spec) 
{ 
	Loop *lb; 
	Queue *q; 
	Chan *c; 
	int chan; 
2000/0708    
	int dev; 
2000/0617    
 
2000/0708    
	if(!havecycintr()) 
		error("can't time packets"); 
 
	dev = 0; 
	if(spec != nil){ 
		dev = atoi(spec); 
		if(dev >= Nloopbacks) 
			error(Ebadspec); 
	} 
 
2000/0617    
	c = devattach('X', spec); 
2000/0708    
	lb = &loopbacks[dev]; 
2000/0617    
 
	qlock(lb); 
	if(waserror()){ 
2000/0708    
		lb->ref--; 
2000/0617    
		qunlock(lb); 
		nexterror(); 
	} 
 
	lb->ref++; 
	if(lb->ref == 1){ 
2000/0708    
		fastticks(&fasthz); 
2000/0617    
		for(chan = 0; chan < 2; chan++){ 
2000/0708    
			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); 
2000/0617    
			lb->link[chan].iq = q; 
			if(q == nil){ 
				freelb(lb); 
				exhausted("memory"); 
			} 
2000/0708    
			q = qopen(lb->link[chan].limit, 0, 0, 0); 
2000/0617    
			lb->link[chan].oq = q; 
			if(q == nil){ 
				freelb(lb); 
				exhausted("memory"); 
			} 
2000/0708    
			lb->link[chan].indrop = 1; 
 
			lb->link[chan].delayn = NS2FASTHZ(Delayn); 
			lb->link[chan].delaynns = Delayn; 
			lb->link[chan].delay0 = NS2FASTHZ(Delay0); 
			lb->link[chan].delay0ns = Delay0; 
2000/0617    
		} 
	} 
	poperror(); 
	qunlock(lb); 
 
2000/0708    
	c->qid = (Qid){CHDIR|QID(0, Qtopdir), 0}; 
2000/0617    
	c->aux = lb; 
	c->dev = 0; 
	return c; 
} 
 
static Chan* 
loopbackclone(Chan *c, Chan *nc) 
{ 
	Loop *lb; 
 
	lb = c->aux; 
	nc = devclone(c, nc); 
	qlock(lb); 
	lb->ref++; 
2000/0708    
	if((c->flag & COPEN) && TYPE(c->qid.path) == Qdata) 
		lb->link[ID(c->qid.path)].ref++; 
2000/0617    
	qunlock(lb); 
	return nc; 
} 
 
static int 
2000/0708    
loopbackgen(Chan *c, Dirtab*, int, int i, Dir *dp) 
2000/0617    
{ 
	Loop *lb; 
2000/0708    
	Dirtab *tab; 
	char buf[NAMELEN]; 
	int len, type; 
2000/0617    
 
2000/0708    
	type = TYPE(c->qid.path); 
2000/0617    
	if(i == DEVDOTDOT){ 
2000/0708    
		switch(type){ 
		case Qtopdir: 
		case Qloopdir: 
			snprint(buf, sizeof(buf), "#X%ld", c->dev); 
			devdir(c, (Qid){CHDIR|QID(0, Qtopdir), 0}, buf, 0, eve, 0555, dp); 
			break; 
		case Qportdir: 
			snprint(buf, sizeof(buf), "loopback%ld", c->dev); 
			devdir(c, (Qid){CHDIR|QID(0, Qloopdir), 0}, buf, 0, eve, 0555, dp); 
			break; 
		default: 
			panic("loopbackgen %lux", c->qid.path); 
		} 
2000/0617    
		return 1; 
	} 
 
2000/0708    
	switch(type){ 
	case Qtopdir: 
		if(i != 0) 
			return -1; 
		snprint(buf, sizeof(buf), "loopback%ld", c->dev); 
		devdir(c, (Qid){QID(0, Qloopdir) | CHDIR, 0}, buf, 0, eve, 0555, dp); 
		return 1; 
	case Qloopdir: 
		if(i >= 2) 
			return -1; 
		snprint(buf, sizeof(buf), "%d", i); 
		devdir(c, (Qid){QID(i, QID(0, Qportdir)) | CHDIR, 0}, buf, 0, eve, 0555, dp); 
		return 1; 
	case Qportdir: 
		if(i >= nelem(loopportdir)) 
			return -1; 
		tab = &loopportdir[i]; 
		devdir(c, (Qid){QID(ID(c->qid.path), tab->qid.path), 0}, tab->name, tab->length, eve, tab->perm, dp); 
		return 1; 
2000/0617    
	default: 
2000/0708    
		/* non directory entries end up here; must be in lowest level */ 
		if(c->qid.path & CHDIR) 
			panic("loopbackgen: unexpected directory");	 
		if(i != 0) 
			return -1; 
		tab = &loopdirs[type]; 
		if(tab == nil) 
			panic("loopbackgen: unknown type: %d", type); 
2000/0617    
		len = tab->length; 
2000/0708    
		if(type == Qdata){ 
			lb = c->aux; 
			len = qlen(lb->link[ID(c->qid.path)].iq); 
		} 
		devdir(c, c->qid, tab->name, len, eve, tab->perm, dp); 
		return 1; 
2000/0617    
	} 
} 
 
 
static int 
loopbackwalk(Chan *c, char *name) 
{ 
2000/0708    
	return devwalk(c, name, nil, 0, loopbackgen); 
2000/0617    
} 
 
static void 
loopbackstat(Chan *c, char *db) 
{ 
2000/0708    
	devstat(c, db, nil, 0, loopbackgen); 
2000/0617    
} 
 
/* 
 *  if the stream doesn't exist, create it 
 */ 
static Chan* 
loopbackopen(Chan *c, int omode) 
{ 
	Loop *lb; 
 
	if(c->qid.path & CHDIR){ 
		if(omode != OREAD) 
			error(Ebadarg); 
		c->mode = omode; 
		c->flag |= COPEN; 
		c->offset = 0; 
		return c; 
	} 
 
	lb = c->aux; 
	qlock(lb); 
2000/0708    
	if(TYPE(c->qid.path) == Qdata) 
		lb->link[ID(c->qid.path)].ref++; 
2000/0617    
	qunlock(lb); 
 
	c->mode = openmode(omode); 
	c->flag |= COPEN; 
	c->offset = 0; 
	return c; 
} 
 
static void 
loopbackclose(Chan *c) 
{ 
	Loop *lb; 
	int ref, chan; 
 
	lb = c->aux; 
 
	qlock(lb); 
2000/0708    
 
	/* 
	 * 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); 
2000/0617    
		} 
	} 
 
 
	/* 
	 *  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); 
2000/0708    
			qsetlimit(lb->link[chan].oq, lb->link[chan].limit); 
			qsetlimit(lb->link[chan].iq, lb->link[chan].limit); 
2000/0617    
		} 
	} 
	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; 
2000/0708    
	cycintrdel(&link->ci); 
2000/0617    
	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 
2000/0708    
loopbackread(Chan *c, void *va, long n, vlong offset) 
2000/0617    
{ 
	Loop *lb; 
2000/0708    
	Link *link; 
	char *buf; 
	long rv; 
2000/0617    
 
	lb = c->aux; 
2000/0708    
	switch(TYPE(c->qid.path)){ 
2000/0617    
	default: 
2000/0708    
		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 %ld %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; 
2000/0617    
	} 
2000/0708    
	return rv; 
2000/0617    
} 
 
static Block* 
loopbackbread(Chan *c, long n, ulong offset) 
{ 
	Loop *lb; 
 
	lb = c->aux; 
2000/0708    
	if(TYPE(c->qid.path) == Qdata) 
		return qbread(lb->link[ID(c->qid.path)].iq, n); 
2000/0617    
 
	return devbread(c, n, offset); 
} 
 
static long 
loopbackbwrite(Chan *c, Block *bp, ulong off) 
{ 
	Loop *lb; 
 
	lb = c->aux; 
2000/0708    
	if(TYPE(c->qid.path) == Qdata) 
		return loopoput(lb, &lb->link[ID(c->qid.path) ^ 1], bp); 
	return devbwrite(c, bp, off); 
2000/0617    
} 
 
static long 
loopbackwrite(Chan *c, void *va, long n, vlong off) 
{ 
2000/0708    
	Loop *lb; 
	Link *link; 
	Cmdbuf *cb; 
2000/0617    
	Block *bp; 
2000/0708    
	long d0, dn, d0ns, dnns; 
2000/0617    
 
2000/0708    
	switch(TYPE(c->qid.path)){ 
	case Qdata: 
2000/0617    
		bp = allocb(n); 
		if(waserror()){ 
			freeb(bp); 
			nexterror(); 
		} 
		memmove(bp->wp, va, n); 
		poperror(); 
		bp->wp += n; 
		return loopbackbwrite(c, bp, off); 
	case Qctl: 
2000/0708    
		lb = c->aux; 
		link = &lb->link[ID(c->qid.path)]; 
		cb = parsecmd(va, n); 
		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 = strtol(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. 
			 */ 
			d0 = NS2FASTHZ(d0ns); 
			dn = NS2FASTHZ(dnns); 
 
			ilock(link); 
			link->delay0 = d0; 
			link->delayn = dn; 
			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: droprate ofn"); 
			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"); 
		break; 
2000/0617    
	default: 
2000/0708    
		error(Eperm); 
2000/0617    
	} 
 
	return n; 
} 
 
static long 
loopoput(Loop *lb, Link *link, Block *bp) 
{ 
	long n; 
 
	n = BLEN(bp); 
 
2000/0708    
	/* make it a single block with space for the loopback timing header */ 
	bp = padblock(bp, Tmsize); 
2000/0617    
	if(bp->next) 
		bp = concatblock(bp); 
	if(BLEN(bp) < lb->minmtu) 
		bp = adjustblock(bp, lb->minmtu); 
2000/0708    
	ptime(bp->rp, fastticks(nil)); 
2000/0617    
 
2000/0708    
	link->packets++; 
	link->bytes += n; 
 
2000/0617    
	qbwrite(link->oq, bp); 
2000/0708    
 
2000/0617    
	looper(lb); 
	return n; 
} 
 
static void 
looper(Loop *lb) 
{ 
2000/0708    
	vlong t; 
	int chan; 
2000/0617    
 
2000/0708    
	t = fastticks(nil); 
	for(chan = 0; chan < 2; chan++) 
		pushlink(&lb->link[chan], t); 
	clockintrsched(); 
2000/0617    
} 
 
2000/0708    
static void 
linkintr(Ureg*, Cycintr *ci) 
{ 
	Link *link; 
 
	link = ci->a; 
	pushlink(link, ci->when); 
} 
 
/* 
 * 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 
2000/0617    
pushlink(Link *link, vlong now) 
{ 
	Block *bp; 
2000/0708    
	vlong tout, tin; 
2000/0617    
 
	/* 
	 * put another block in the link queue 
	 */ 
	ilock(link); 
	if(link->iq == nil || link->oq == nil){ 
		iunlock(link); 
2000/0708    
		return; 
 
2000/0617    
	} 
2000/0708    
	cycintrdel(&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){ 
2000/0617    
		bp = qget(link->oq); 
2000/0708    
		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->delay0); 
2000/0617    
			if(link->tq == nil) 
				link->tq = bp; 
			else 
				link->tqtail->next = bp; 
			link->tqtail = bp; 
2000/0708    
		} 
2000/0617    
	} 
 
	/* 
2000/0708    
	 * 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; 
 
	/* 
2000/0617    
	 * put more blocks into the receive queue 
	 */ 
2000/0708    
	tin = 0; 
2000/0617    
	while(bp = link->tq){ 
2000/0708    
		tin = gtime(bp->rp); 
		if(tin > now) 
2000/0617    
			break; 
2000/0708    
		bp->rp += Tmsize; 
2000/0617    
		link->tq = bp->next; 
		bp->next = nil; 
2000/0708    
		if(!link->indrop) 
2000/0617    
			qpassnolim(link->iq, bp); 
		else if(qpass(link->iq, bp) < 0) 
			link->soverflows++; 
2000/0708    
		tin = 0; 
2000/0617    
	} 
	if(bp == nil && qisclosed(link->oq) && !qcanread(link->oq) && !qisclosed(link->iq)) 
		qhangup(link->iq, nil); 
2000/0708    
	link->tin = tin; 
	if(!tin || tin > tout && tout) 
		tin = tout; 
 
	link->ci.when = tin; 
	if(tin){ 
		if(tin < now) 
			panic("loopback unfinished business"); 
		cycintradd(&link->ci); 
	} 
2000/0617    
	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, 
	loopbackattach, 
	loopbackclone, 
	loopbackwalk, 
	loopbackstat, 
	loopbackopen, 
	devcreate, 
	loopbackclose, 
	loopbackread, 
	loopbackbread, 
	loopbackwrite, 
	loopbackbwrite, 
	devremove, 
	devwstat, 
}; 


source code copyright © 1990-2005 Lucent Technologies; see license
Plan 9 distribution
comments to russ cox (rsc@swtch.com)