File:  [Plan 9 NeXT] / lucent / sys / src / 9 / pc / devether.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 24 18:01:02 2018 UTC (8 years, 1 month ago) by root
Branches: lucent, MAIN
CVS tags: plan9, HEAD
Plan 9 NeXT

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "io.h"
#include "devtab.h"

#include "ether.h"

/*
 * Half-arsed attempt at a general top-level
 * ethernet driver. Needs work:
 *	handle multiple controllers
 *	much tidying
 *	set ethernet address
 *	need a ctl file passed down to card drivers
 *	  so we can set options.
 */

struct Ctlr *softctlr;

Chan*
etherclone(Chan *c, Chan *nc)
{
	return devclone(c, nc);
}

int
etherwalk(Chan *c, char *name)
{
	return netwalk(c, name, &softctlr->net);
}

void
etherstat(Chan *c, char *dp)
{
	netstat(c, dp, &softctlr->net);
}

Chan*
etheropen(Chan *c, int omode)
{
	return netopen(c, omode, &softctlr->net);
}

void
ethercreate(Chan *c, char *name, int omode, ulong perm)
{
	USED(c, name, omode, perm);
	error(Eperm);
}

void
etherclose(Chan *c)
{
	if(c->stream)
		streamclose(c);
}

long
etherread(Chan *c, void *a, long n, ulong offset)
{
	return netread(c, a, n, offset, &softctlr->net);
}

long
etherwrite(Chan *c, char *a, long n, ulong offset)
{
	USED(offset);
	return streamwrite(c, a, n, 0);
}

void
etherremove(Chan *c)
{
	USED(c);
	error(Eperm);
}

void
etherwstat(Chan *c, char *dp)
{
	netwstat(c, dp, &softctlr->net);
}

static int
isobuf(void *arg)
{
	Ctlr *ctlr = arg;

	return ctlr->tb[ctlr->th].owner == Host;
}

static void
etheroput(Queue *q, Block *bp)
{
	Ctlr *ctlr;
	Type *type;
	Etherpkt *pkt;
	RingBuf *ring;
	int len, n, s;
	Block *nbp;
	uchar ea[6];
	char *err;

	type = q->ptr;
	ctlr = type->ctlr;
	if(bp->type == M_CTL){
		err = 0;
		qlock(ctlr);
		if(streamparse("connect", bp)){
			if(type->type == -1)
				ctlr->all--;
			type->type = strtol((char*)bp->rptr, 0, 0);
			if(type->type == -1)
				ctlr->all++;
		}
		else if(streamparse("promiscuous", bp)) {
			if(type->prom)
				goto ctlout;
			if(type->filter){
				err = "address already set";
				goto ctlout;
			}
			type->prom = 1;
			ctlr->prom++;
			if(ctlr->prom+ctlr->filter == 1)
				(*ctlr->card.mode)(ctlr, 1);
		}
		else if(streamparse("address", bp)) {
			if(type->prom){
				err = "already promiscuous";
				goto ctlout;
			}
			if(parseether(ea, bp->rptr) < 0){
				err = "bad ether address";
				goto ctlout;
			}
			memmove(type->ea, ea, sizeof(ea));
			if(!type->filter){
				type->filter = 1;
				ctlr->filter++;
				if(ctlr->filter == 1)
					(*ctlr->card.mode)(ctlr, 3);
			}
		}
	ctlout:
		qunlock(ctlr);
		freeb(bp);
		if(err)
			error(err);
		return;
	}

	/*
	 * Give packet a local address, return upstream if destined for
	 * this machine.
	 */
	if(BLEN(bp) < ETHERHDRSIZE && (bp = pullup(bp, ETHERHDRSIZE)) == 0)
		return;
	pkt = (Etherpkt*)bp->rptr;
	memmove(pkt->s, type->ea, sizeof(type->ea));
	if(memcmp(ctlr->ea, pkt->d, sizeof(ctlr->ea)) == 0){
		len = blen(bp);
		if(bp = expandb(bp, len >= ETHERMINTU ? len: ETHERMINTU)){
			putq(&ctlr->lbq, bp);
			wakeup(&ctlr->rr);
		}
		return;
	}
	if(memcmp(ctlr->ba, pkt->d, sizeof(ctlr->ba)) == 0 || ctlr->prom || ctlr->all){
		len = blen(bp);
		nbp = copyb(bp, len);
		if(nbp = expandb(nbp, len >= ETHERMINTU ? len: ETHERMINTU)){
			nbp->wptr = nbp->rptr+len;
			putq(&ctlr->lbq, nbp);
			wakeup(&ctlr->rr);
		}
	}

	/*
	 * Only one transmitter at a time.
	 */
	qlock(&ctlr->tlock);
	if(waserror()){
		qunlock(&ctlr->tlock);
		freeb(bp);
		nexterror();
	}

	/*
	 * Wait till we get an output buffer.
	 * should try to restart.
	 */
	if(isobuf(ctlr) == 0){
		tsleep(&ctlr->tr, isobuf, ctlr, 3*1000);
		if(isobuf(ctlr) == 0){
			qunlock(&ctlr->tlock);
			freeb(bp);
			poperror();
			return;
		}
	}

	ring = &ctlr->tb[ctlr->th];

	/*
	 * Copy message into buffer.
	 */
	len = 0;
	for(nbp = bp; nbp; nbp = nbp->next){
		if(sizeof(Etherpkt) - len >= (n = BLEN(nbp))){
			memmove(ring->pkt+len, nbp->rptr, n);
			len += n;
		}
		if(bp->flags & S_DELIM)
			break;
	}

	/*
	 * Pad the packet (zero the pad).
	 */
	if(len < ETHERMINTU){
		memset(ring->pkt+len, 0, ETHERMINTU-len);
		len = ETHERMINTU;
	}

	/*
	 * Set up the transmit buffer and 
	 * start the transmission.
	 */
	s = splhi();
	ring->len = len;
	ring->owner = Interface;
	ctlr->th = NEXT(ctlr->th, ctlr->ntb);
	(*ctlr->card.transmit)(ctlr);
	ctlr->outpackets++;
	splx(s);

	qunlock(&ctlr->tlock);
	freeb(bp);
	poperror();
}

/*
 * Open an ether line discipline.
 */
static void
etherstopen(Queue *q, Stream *s)
{
	Ctlr *ctlr = softctlr;
	Type *type;

	type = &ctlr->type[s->id];
	RD(q)->ptr = WR(q)->ptr = type;
	type->type = 0;
	type->q = RD(q);
	type->inuse = 1;
	memmove(type->ea, ctlr->ea, sizeof(type->ea));
	type->ctlr = ctlr;
}

/*
 * Close ether line discipline.
 *
 * The locking is to synchronize changing the ethertype with
 * sending packets up the stream on interrupts.
 */
static int
isclosed(void *arg)
{
	return ((Type*)arg)->q == 0;
}

static void
etherstclose(Queue *q)
{
	Type *type = (Type*)(q->ptr);
	Ctlr *ctlr = type->ctlr;

	if(type->prom){
		qlock(ctlr);
		ctlr->prom--;
		if(ctlr->prom+ctlr->filter == 0)
			(*ctlr->card.mode)(ctlr, 0);
		qunlock(ctlr);
	}
	if(type->filter){
		qlock(ctlr);
		ctlr->filter--;
		if(ctlr->filter == 0)
			(*ctlr->card.mode)(ctlr, ctlr->prom ? 1 : 0);
		qunlock(ctlr);
	}
	if(type->type == -1){
		qlock(ctlr);
		ctlr->all--;
		qunlock(ctlr);
	}

	/*
	 * Mark as closing and wait for kproc
	 * to close us.
	 */
	lock(&ctlr->clock);
	type->clist = ctlr->clist;
	ctlr->clist = type;
	unlock(&ctlr->clock);
	wakeup(&ctlr->rr);
	sleep(&type->cr, isclosed, type);

	type->type = 0;
	type->prom = 0;
	type->filter = 0;
	type->inuse = 0;
	netdisown(type);
	type->ctlr = 0;
}

static Qinfo info = {
	nullput,
	etheroput,
	etherstopen,
	etherstclose,
	"ether"
};

static int
clonecon(Chan *c)
{
	Ctlr *ctlr = softctlr;
	Type *type;

	USED(c);
	for(type = ctlr->type; type < &ctlr->type[NType]; type++){
		qlock(type);
		if(type->inuse || type->q){
			qunlock(type);
			continue;
		}
		type->inuse = 1;
		memmove(type->ea, ctlr->ea, sizeof(type->ea));
		netown(type, u->p->user, 0);
		qunlock(type);
		return type - ctlr->type;
	}
	exhausted("ether channels");
	return 0;
}

static void
statsfill(Chan *c, char *p, int n)
{
	Ctlr *ctlr = softctlr;
	char buf[256];

	USED(c);
	sprint(buf, "in: %d\nout: %d\ncrc errs %d\noverflows: %d\nframe errs %d\nbuff errs: %d\noerrs %d\naddr: %.02x:%.02x:%.02x:%.02x:%.02x:%.02x\n",
		ctlr->inpackets, ctlr->outpackets, ctlr->crcs,
		ctlr->overflows, ctlr->frames, ctlr->buffs, ctlr->oerrs,
		ctlr->ea[0], ctlr->ea[1], ctlr->ea[2],
		ctlr->ea[3], ctlr->ea[4], ctlr->ea[5]);
	strncpy(p, buf, n);
}

static void
typefill(Chan *c, char *p, int n)
{
	char buf[16];
	Type *type;

	type = &softctlr->type[STREAMID(c->qid.path)];
	sprint(buf, "%d", type->type);
	strncpy(p, buf, n);
}

int
eaddrmatch(Ctlr *ctlr, uchar *ea)
{
	Type *type;

	for(type = &ctlr->type[0]; type < &ctlr->type[NType]; type++){
		if(type->q == 0 || ea[0] != type->ea[0])
			continue;
		if(memcmp(ea, type->ea, sizeof(type->ea)) == 0)
			return 1;
	}
	return 0;
}

static void
etherup(Ctlr *ctlr, Etherpkt *pkt, int len)
{
	int t;
	Type *type;
	Block *bp;

	t = (pkt->type[0]<<8)|pkt->type[1];
	for(type = &ctlr->type[0]; type < &ctlr->type[NType]; type++){

		/*
		 * Check for open, the right type, and flow control.
		 */
		if(type->q == 0)
			continue;
		if(t != type->type && type->type >= 0)
			continue;
		if(type->q->next->len > Streamhi)
			continue;

		/*
		 * Only a trace channel gets packets destined for other machines.
		 */
		if(type->type != -1 && pkt->d[0] != 0xFF
		  && (*pkt->d != *type->ea || memcmp(pkt->d, type->ea, sizeof(pkt->d))))
			continue;

		if(waserror() == 0){
			bp = allocb(len);
			memmove(bp->rptr, pkt, len);
			bp->wptr += len;
			bp->flags |= S_DELIM;
			PUTNEXT(type->q, bp);
			poperror();
		}
	}
}

static int
isinput(void *arg)
{
	Ctlr *ctlr = arg;

	return ctlr->lbq.first || ctlr->rb[ctlr->rh].owner == Host || ctlr->clist;
}

static void
etherkproc(void *arg)
{
	Ctlr *ctlr = arg;
	RingBuf *ring;
	Block *bp;
	Type *type;

	if(waserror()){
		print("%s noted\n", ctlr->name);
		/* fix
		if(ctlr->card.reset)
			(*ctlr->card.reset)(ctlr);
		 */
		ctlr->kproc = 0;
		nexterror();
	}

	for(;;){
		tsleep(&ctlr->rr, isinput, ctlr, 500);
		if(ctlr->card.watch)
			(*ctlr->card.watch)(ctlr);

		/*
		 * Process any internal loopback packets.
		 */
		while(bp = getq(&ctlr->lbq)){
			ctlr->inpackets++;
			etherup(ctlr, (Etherpkt*)bp->rptr, BLEN(bp));
			freeb(bp);
		}

		/*
		 * Process any received packets.
		 */
		while(ctlr->rb[ctlr->rh].owner == Host){
			ctlr->inpackets++;
			ring = &ctlr->rb[ctlr->rh];
			etherup(ctlr, (Etherpkt*)ring->pkt, ring->len);
			ring->owner = Interface;
			ctlr->rh = NEXT(ctlr->rh, ctlr->nrb);
		}

		/*
		 * Close Types requesting it.
		 */
		if(ctlr->clist){
			lock(&ctlr->clock);
			for(type = ctlr->clist; type; type = type->clist){
				type->q = 0;
				wakeup(&type->cr);
			}
			ctlr->clist = 0;
			unlock(&ctlr->clock);
		}
	}
}

static void
etherintr(Ureg *ur, void *a)
{
	Ctlr *ctlr = softctlr;

	USED(ur, a);
	(*ctlr->card.intr)(ctlr);
}

static void
reset(Ctlr *ctlr)
{
	int i;

	if(ctlr->nrb == 0)
		ctlr->nrb = Nrb;
	ctlr->rb = xalloc(sizeof(RingBuf)*ctlr->nrb);
	if(ctlr->ntb == 0)
		ctlr->ntb = Ntb;
	ctlr->tb = xalloc(sizeof(RingBuf)*ctlr->ntb);

	memset(ctlr->ba, 0xFF, sizeof(ctlr->ba));

	ctlr->net.name = "ether";
	ctlr->net.nconv = NType;
	ctlr->net.devp = &info;
	ctlr->net.protop = 0;
	ctlr->net.listen = 0;
	ctlr->net.clone = clonecon;
	ctlr->net.ninfo = 2;
	ctlr->net.info[0].name = "stats";
	ctlr->net.info[0].fill = statsfill;
	ctlr->net.info[1].name = "type";
	ctlr->net.info[1].fill = typefill;
	for(i = 0; i < NType; i++)
		netadd(&ctlr->net, &ctlr->type[i], i);
}

extern int wd8003reset(Ctlr*);
extern int ne2000reset(Ctlr*);
extern int ccc509reset(Ctlr*);
extern int nsciareset(Ctlr*);
extern int ne2000PCMreset(Ctlr*);

#define NCARD 32
struct {
	char	*type;
	int	(*reset)(Ctlr*);
} cards[NCARD+1];

void
addethercard(char *t, int (*r)(Ctlr*))
{
	static int ncard;

	if(ncard == NCARD)
		panic("too many ether cards");
	cards[ncard].type = t;
	cards[ncard].reset = r;
	ncard++;
}

void
etherreset(void)
{
	Ctlr *ctlr;
	int i, n, ctlrno;

	if(softctlr == 0)
		softctlr = xalloc(sizeof(Ctlr));
	else
		memset(softctlr, 0, sizeof(Ctlr));
	ctlr = softctlr;

	for(ctlrno = i = 0; isaconfig("ether", i, &ctlr->card); i++){
		for(n = 0; cards[n].type; n++){
			if(strcmp(cards[n].type, ctlr->card.type))
				continue;
			ctlr->ctlrno = ctlrno;
			memmove(ctlr->ea, ctlr->card.ea, sizeof(ctlr->ea));
			if((*cards[n].reset)(ctlr))
				break;

			/*ctlrno++;*/
			ctlr->present = 1;
			/*
			 * IRQ2 doesn't really exist, it's used to gang the interrupt
			 * controllers together. A device set to IRQ2 will appear on
			 * the second interrupt controller as IRQ9.
			 */
			if(ctlr->card.irq == 2)
				ctlr->card.irq = 9;
			setvec(Int0vec + ctlr->card.irq, etherintr, 0);

			print("ether%d:%s: port %lux irq %d addr %lux size %d width %d:",
				ctlr->ctlrno, ctlr->card.type, ctlr->card.port, ctlr->card.irq,
				ctlr->card.mem, ctlr->card.size, ctlr->card.bit16 ? 16: 8);
			for(i = 0; i < sizeof(ctlr->ea); i++)
				print("%2.2ux", ctlr->ea[i]);
			print("\n");

			reset(ctlr);
			return;
		}
		memset(softctlr, 0, sizeof(Ctlr));
	}
}

void
etherinit(void)
{
	Ctlr *ctlr = softctlr;
	int i;

	if(ctlr->present == 0)
		return;

	ctlr->rh = 0;
	ctlr->ri = 0;
	for(i = 0; i < ctlr->nrb; i++)
		ctlr->rb[i].owner = Interface;

	ctlr->th = 0;
	ctlr->ti = 0;
	for(i = 0; i < ctlr->ntb; i++)
		ctlr->tb[i].owner = Host;
}

Chan*
etherattach(char *spec)
{
	Ctlr *ctlr = softctlr;

	if(ctlr->present == 0)
		error(Enodev);

	/*
	 * Enable the interface
	 * and start the kproc.
	 */	
	(*ctlr->card.attach)(ctlr);
	if(ctlr->kproc == 0){
		sprint(ctlr->name, "ether%dkproc", 0);
		ctlr->kproc = 1;
		kproc(ctlr->name, etherkproc, ctlr);
	}
	return devattach('l', spec);
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.