File:  [Plan 9 NeXT] / lucent / sys / src / 9 / port / devlance.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs
Tue Apr 24 18:01:03 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	"io.h"
#include	"../port/error.h"
#include	"devtab.h"

enum {
	Ntypes=		9,		/* max number of ethernet packet types */
	Maxrb=		128,		/* max buffers in a ring */
};
#define RSUCC(x) (((x)+1)%l.nrrb)
#define TSUCC(x) (((x)+1)%l.ntrb)

/*
 *  Communication with the lance is via a transmit and receive ring of
 *  message descriptors.  The Initblock contains pointers to and sizes of
 *  these rings.  The rings must be in RAM addressible by the lance
 */
typedef struct {
	ushort	laddr;		/* low order piece of address */
	ushort	flags;		/* flags and high order piece of address */
	short	size;		/* size of buffer */
	ushort	cntflags;	/* (rcv)count of bytes in buffer; (xmt) more flags */
} Msg;

/*
 *  lance memory map
 */
struct Lancemem
{
	/*
	 *  initialization block
	 */
	ushort	mode;		/* chip control (see below) */
	ushort	etheraddr[3];	/* the ethernet physical address */
	ushort	multi[4];	/* multicast addresses, 1 bit for each of 64 */
	ushort	rdralow;	/* receive buffer ring */
	ushort	rdrahigh;	/* (top three bits define size of ring) */
	ushort	tdralow;	/* transmit buffer ring */
	ushort	tdrahigh;	/* (top three bits define size of ring) */
	
	/*
	 *  ring buffers
	 *  first receive, then transmit
	 */
	Msg	rmr[Maxrb];		/* recieve message ring */
	Msg	tmr[Maxrb];		/* transmit message ring */
};

/*
 *  Some macros for dealing with lance memory addresses.  The lance splits
 *  its 24 bit addresses across two 16 bit registers.
 */
#define HADDR(a) ((((ulong)(a))>>16)&0xFF)
#define LADDR(a) (((ulong)a)&0xFFFF)

/*
 *  The following functions exist to sidestep a quirk in the SGI IO3 lance
 *  interface.  In all other processors, the lance's initialization block and
 *  descriptor rings look like normal memory.  In the SGI IO3, the CPU sees a
 *  6 byte pad twixt all lance memory shorts.  Therefore, we use the following
 *  macros to compute the address whenever accessing the lance memory to make
 *  the code portable.  Sic transit gloria.
 */
#define LANCEMEM ((Lancemem*)0)
#define MPs(a) (*(short *)(l.lanceram + l.sep*((ushort*)&a - (ushort*)0)))
#define MPus(a) (*(ushort *)(l.lanceram + l.sep*((ushort*)&a - (ushort*)0)))

/*
 *  one per ethernet packet type
 */
typedef struct Ethertype	Ethertype;
struct Ethertype
{
	QLock;
	Netprot;			/* stat info */
	int		type;		/* ethernet type */
	int		prom;		/* promiscuous mode */
	Queue		*q;
	int		inuse;
	Rendez		rc;		/* rendzvous for close */
	Ethertype	*closeline;	/* close list */
};


/*
 *  lance state
 */
typedef struct {
	QLock;

	Lance;			/* host dependent lance params */
	int	prom;		/* number of promiscuous channels */
	int	all;		/* number of channels listening to all packets */
	int	wedged;		/* the lance is wedged */
	Network	net;

	int	inited;
	uchar	*lmp;		/* location of parity test */

	Rendez	rr;		/* rendezvous for an input buffer */
	QLock	rlock;		/* semaphore on tc */
	ushort	rl;		/* first rcv Message belonging to Lance */	

	Rendez	tr;		/* rendezvous for an output buffer */
	QLock	tlock;		/* semaphore on tc */
	ushort	tc;		/* next xmt Message CPU will try for */	

	Ethertype *closeline;	/* channels waiting to close */
	Lock	closepin;	/* lock for closeline */
	Ethertype e[Ntypes];
	int	debug;
	int	kstarted;
	uchar	bcast[6];

	Queue	self;	/* packets turned around at the interface */

	/* sadistics */

	int	misses;
	int	inpackets;
	int	outpackets;
	int	crcs;		/* input crc errors */
	int	oerrs;		/* output erros */
	int	frames;		/* framing errors */
	int	overflows;	/* packet overflows */
	int	buffs;		/* buffering errors */
} SoftLance;
static SoftLance l;

/*
 *  mode bits in the lance initialization block
 */
#define PROM	0x8000
#define INTL	0x40
#define DRTY	0x20
#define COLL	0x10
#define DTCR	0x8
#define LOOP	0x4
#define DTX	0x2
#define DRX	0x1

/*
 *  LANCE CSR0, this is the register we play with most often.  We leave
 *  this register pointed to by l.rap in normal operation.
 */
#define ERR0	0x8000
#define BABL	0x4000
#define CERR	0x2000
#define MISS	0x1000
#define MERR	0x800
#define RINT	0x400
#define TINT	0x200
#define IDON	0x100
#define INTR	0x80
#define INEA	0x40
#define RXON	0x20
#define TXON	0x10
#define TDMD	0x8
#define STOP	0x4
#define STRT	0x2
#define INIT	0x1

/*
 *  flag bits from a buffer descriptor in the rcv/xmt rings
 */
#define LANCEOWNER	0x8000	/* 1 means that the buffer can be used by the chip */
#define ERR	0x4000	/* error summary, the OR of all error bits */
#define FRAM	0x2000	/* CRC error and incoming packet not a multiple of 8 bits */
#define OFLO	0x1000	/* (receive) lost some of the packet */
#define MORE	0x1000	/* (transmit) more than 1 retry to send the packet */
#define CRC	0x800	/* (receive) crc error reading packet */
#define ONE	0x800	/* (transmit) one retry to transmit the packet */
#define BUF	0x400	/* (receive) out of buffers while reading a packet */
#define DEF	0x400	/* (transmit) deffered while transmitting packet */
#define STP	0x200	/* start of packet */
#define ENP	0x100	/* end of packet */

/*
 *  cntflags bits from a buffer descriptor in the rcv/xmt rings
 */
#define BUFF	0x8000	/* buffer error (host screwed up?) */
#define UFLO	0x4000	/* underflow from memory */
#define LCOL	0x1000	/* late collision (ether too long?) */
#define LCAR	0x800	/* loss of carrier (ether broken?) */
#define RTRY	0x400	/* couldn't transmit (bad station on ether?) */
#define TTDR	0x3FF	/* time domain reflectometer */

/*
 *  predeclared
 */
static void	lancekproc(void *);
static void	lancestart(int);
static void	lanceup(Etherpkt*, int);
static int	lanceclonecon(Chan*);
static void	lancestatsfill(Chan*, char*, int);
static void	lancetypefill(Chan*, char*, int);

/*
 *  lance stream module definition
 */
static void lanceoput(Queue*, Block*);
static void lancestopen(Queue*, Stream*);
static void lancestclose(Queue*);
static void stagerbuf(void);
Qinfo lanceinfo = { nullput, lanceoput, lancestopen, lancestclose, "lance" };

/*
 *  open a lance line discipline
 */
void
lancestopen(Queue *q, Stream *s)
{
	Ethertype *et;

	et = &l.e[s->id];
	RD(q)->ptr = WR(q)->ptr = et;
	et->type = 0;
	et->q = RD(q);
	et->inuse = 1;
}

/*
 *  close lance line discipline
 *
 *  the lock is to synchronize changing the ethertype with
 *  sending packets up the stream on interrupts.
 */
static int
isclosed(void *x)
{
	return ((Ethertype *)x)->q == 0;
}

static void
lancestclose(Queue *q)
{
	Ethertype *et;

	et = (Ethertype *)(q->ptr);
	if(et->prom){
		qlock(&l);
		l.prom--;
		if(l.prom == 0)
			lancestart(0);
		qunlock(&l);
	}
	if(et->type == -1){
		qlock(&l);
		l.all--;
		qunlock(&l);
	}

	/*
	 *  mark as closing and wait for kproc to close us
	 */
	lock(&l.closepin);
	et->closeline = l.closeline;
	l.closeline = et;
	unlock(&l.closepin);
	wakeup(&l.rr);
	sleep(&et->rc, isclosed, et);
	
	et->type = 0;
	et->q = 0;
	et->prom = 0;
	et->inuse = 0;
	netdisown(et);
}

/*
 *  the ``connect'' control message specifyies the type
 */
Proc *lanceout;
static int
isobuf(void *x)
{
	Msg *m;

	m = x;
	return l.wedged || (MPus(m->flags)&LANCEOWNER) == 0;
}

static void
lanceoput(Queue *q, Block *bp)
{
	int n, len;
	Etherpkt *p;
	Ethertype *e;
	Msg *m;
	Block *nbp;

	if(bp->type == M_CTL){
		e = q->ptr;
		qlock(&l);
		if(streamparse("connect", bp)){
			if(e->type == -1)
				l.all--;
			e->type = strtol((char *)bp->rptr, 0, 0);
			if(e->type == -1)
				l.all++;
		} else if(streamparse("promiscuous", bp)) {
			e->prom = 1;
			l.prom++;
			if(l.prom == 1)
				lancestart(PROM);/**/
		}
		qunlock(&l);
		freeb(bp);
		return;
	}

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

	if(l.wedged) {
		freeb(bp);
		return;
	}

	/*
	 *  only one transmitter at a time
	 */
	qlock(&l.tlock);

	if(l.wedged) {
		qunlock(&l.tlock);
		freeb(bp);
		return;
	}

	if(waserror()){
		qunlock(&l.tlock);
		freeb(bp);
		nexterror();
	}

	/*
	 *  Wait till we get an output buffer, complain if input
	 *  or output seems wedged.
	 */
	m = &(LANCEMEM->tmr[l.tc]);
	p = &l.tp[l.tc];
	while((MPus(m->flags)&LANCEOWNER) != 0) {
		tsleep(&l.tr, isobuf, m, 128);
		if(l.wedged || isobuf(m) == 0){
			qunlock(&l.tlock);
			freeb(bp);
			poperror();
			print("lance wedged, dumping block & restarting\n");
			lancestart(0);
			l.wedged = 0;
			return;
		}
	}

	/*
	 *  copy message into lance RAM
	 */
	len = 0;
	for(nbp = bp; nbp; nbp = nbp->next){
		if(sizeof(Etherpkt) - len >= (n = BLEN(nbp))){
			memmove(((uchar *)p)+len, nbp->rptr, n);
			len += n;
		} else
			print("no room damn it\n");
		if(bp->flags & S_DELIM)
			break;
	}

	/*
	 *  pad the packet (zero the pad)
	 */
	if(len < ETHERMINTU){
		memset(((char*)p)+len, 0, ETHERMINTU-len);
		len = ETHERMINTU;
	}

	/*
	 *  set up the ring descriptor and hand to lance
	 */
	l.outpackets++;
	MPs(m->size) = -len;
	MPus(m->cntflags) = 0;
	MPus(m->laddr) = LADDR(&l.ltp[l.tc]);
	MPus(m->flags) = LANCEOWNER|STP|ENP|HADDR(&l.ltp[l.tc]);
	l.tc = TSUCC(l.tc);
	*l.rdp = INEA|TDMD; /**/
	wbflush();
	qunlock(&l.tlock);
	freeb(bp);
	poperror();
}

/*
 *  stop the lance and allocate buffers
 */
void
lancereset(void)
{
	static int already;
	int i;

	if(already == 0){
		already = 1;
		lancesetup(&l);

		l.net.name = "ether";
		l.net.nconv = Ntypes;
		l.net.devp = &lanceinfo;
		l.net.protop = 0;
		l.net.listen = 0;
		l.net.clone = lanceclonecon;
		l.net.ninfo = 2;
		l.net.info[0].name = "stats";
		l.net.info[0].fill = lancestatsfill;
		l.net.info[1].name = "type";
		l.net.info[1].fill = lancetypefill;
		for(i = 0; i < Ntypes; i++)
			netadd(&l.net, &l.e[i], i);

		memset(l.bcast, 0xff, sizeof l.bcast);
	}

	/*
	 *  stop the lance
	 */
	*l.rap = 0;
	*l.rdp = STOP;
	l.wedged = 1;
}

/*
 *  Initialize and start the lance.  This routine can be called only from a process.
 *  It may be used to restart a dead lance.
 */
static void
lancestart(int mode)
{
	int i;
	Lancemem *lm = LANCEMEM;
	Msg *m;

	/*
	 *   wait till both receiver and transmitter are
	 *   quiescent
	 */
	qlock(&l.tlock);
	qlock(&l.rlock);

	lancereset();
	l.rl = 0;
	l.tc = 0;

	/*
	 *  create the initialization block
	 */
	MPus(lm->mode) = mode;

	/*
	 *  set ether addr from the value in the id prom.
	 *  the id prom has them in reverse order, the init
	 *  structure wants them in byte swapped order
	 */
	MPus(lm->etheraddr[0]) = (l.ea[1]<<8) | l.ea[0];
	MPus(lm->etheraddr[1]) = (l.ea[3]<<8) | l.ea[2];
	MPus(lm->etheraddr[2]) = (l.ea[5]<<8) | l.ea[4];

	/*
	 *  ignore multicast addresses
	 */
	MPus(lm->multi[0]) = 0;
	MPus(lm->multi[1]) = 0;
	MPus(lm->multi[2]) = 0;
	MPus(lm->multi[3]) = 0;

	/*
	 *  set up rcv message ring
	 */
	m = lm->rmr;
	for(i = 0; i < l.nrrb; i++, m++){
		MPs(m->size) = -sizeof(Etherpkt);
		MPus(m->cntflags) = 0;
		MPus(m->laddr) = LADDR(&l.lrp[i]);
		MPus(m->flags) = HADDR(&l.lrp[i]);
	}
	MPus(lm->rdralow) = LADDR(l.lm->rmr);
	MPus(lm->rdrahigh) = (l.lognrrb<<13)|HADDR(l.lm->rmr);


	/*
	 *  give the lance all the rcv buffers except one (as a sentinel)
	 */
	m = lm->rmr;
	for(i = 0; i < l.nrrb; i++, m++)
		MPus(m->flags) |= LANCEOWNER;

	/*
	 *  set up xmit message ring
	 */
	m = lm->tmr;
	for(i = 0; i < l.ntrb; i++, m++){
		MPs(m->size) = 0;
		MPus(m->cntflags) = 0;
		MPus(m->laddr) = LADDR(&l.ltp[i]);
		MPus(m->flags) = HADDR(&l.ltp[i]);
	}
	MPus(lm->tdralow) = LADDR(l.lm->tmr);
	MPus(lm->tdrahigh) = (l.logntrb<<13)|HADDR(l.lm->tmr);

	/*
	 *  point lance to the initialization block
	 */
	*l.rap = 1;
	*l.rdp = LADDR(l.lm);
	wbflush();
	*l.rap = 2;
	*l.rdp = HADDR(l.lm);

	/*
	 *  The lance byte swaps the ethernet packet unless we tell it not to
	 */
	wbflush();
	*l.rap = 3;
	*l.rdp = l.busctl;

	/*
	 *  initialize lance, turn on interrupts, turn on transmit and rcv.
	 */
	wbflush();
	*l.rap = 0;
	*l.rdp = INEA|INIT|STRT; /**/

	/*
	 *  spin for up to a second waiting for the IDON interrupt
	 */
	for(i = 0; i < 1000; i++){
		if(l.wedged == 0)
			break;
		delay(1);
	}

	/*
	 *  let in everything else
	 */
	qunlock(&l.rlock);
	qunlock(&l.tlock);
}

void
lanceinit(void)
{
}

Chan*
lanceattach(char *spec)
{
	if(l.kstarted == 0){
		kproc("lancekproc", lancekproc, 0);
		l.kstarted = 1;
		lancestart(0);
		print("lance ether: %.2x%.2x%.2x%.2x%.2x%.2x\n",
			l.ea[0], l.ea[1], l.ea[2], l.ea[3], l.ea[4], l.ea[5]);
	}
	return devattach('l', spec);
}

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

int	 
lancewalk(Chan *c, char *name)
{
	return netwalk(c, name, &l.net);
}

void	 
lancestat(Chan *c, char *dp)
{
	netstat(c, dp, &l.net);
}

/*
 *  Pass open's of anything except the directory to streamopen
 */
Chan*
lanceopen(Chan *c, int omode)
{
	return netopen(c, omode, &l.net);
}

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

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

long	 
lanceread(Chan *c, void *a, long n, ulong offset)
{
	return netread(c, a, n, offset,  &l.net);
}

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

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

void	 
lancewstat(Chan *c, char *dp)
{
	netwstat(c, dp, &l.net);
}

/*
 *  user level network interface routines
 */
static void
lancestatsfill(Chan *c, char* p, int n)
{
	char buf[512];

	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",
		l.inpackets, l.outpackets, l.crcs,
		l.overflows, l.frames, l.buffs, l.oerrs,
		l.ea[0], l.ea[1], l.ea[2], l.ea[3], l.ea[4], l.ea[5]);
	strncpy(p, buf, n);
}

static void
lancetypefill(Chan *c, char* p, int n)
{
	char buf[16];
	Ethertype *e;

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

static int
lanceclonecon(Chan *c)
{
	Ethertype *e;

	USED(c);
	for(e = l.e; e < &l.e[Ntypes]; e++){
		qlock(e);
		if(e->inuse || e->q){
			qunlock(e);
			continue;
		}
		e->inuse = 1;
		netown(e, u->p->user, 0);
		qunlock(e);
		return e - l.e;
	}
	error(Enodev);
	return -1;		/* never reached */
}

/*
 *  We will:
 *	(1) Clear interrupt cause in the lance
 *	(2) service all current events
 */
void
lanceintr(void)
{
	ushort csr;

	csr = *l.rdp;

	/*
	 *  turn off the interrupt and any error indicators
	 */
	*l.rdp = IDON|INEA|TINT|RINT|BABL|CERR|MISS|MERR;

	/*
	 *  see if an error occurred
	 */
	if(csr & (BABL|MISS|MERR)){
		if(l.misses++ < 4) {
			print("lance err #%ux\n", csr);
		} else {
			print("lance stopped\n");
			l.wedged = 1;
			l.misses = 0;
			lancereset();
			wakeup(&l.rr);
			wakeup(&l.tr);
			return;
		}
	}

	/*
	 *  initialization done
	 */
	if(csr & IDON)
		l.wedged = 0;

	/*
	 *  the lance turns off if it gets strange output errors
	 */
	if((csr & (TXON|RXON)) != (TXON|RXON))
		l.wedged = 1;

	/*
	 *  wakeup the input process
	 */
	if(csr & RINT)
		wakeup(&l.rr);

	/*
	 *  wake any process waiting for a transmit buffer
	 */
	if(csr & TINT)
		wakeup(&l.tr);
}

/*
 *  send a packet upstream
 */
static void
lanceup(Etherpkt *p, int len)
{
	int t;
	Block *bp;
	Ethertype *e;

	if(len <= 0)
		return;

	t = (p->type[0]<<8) | p->type[1];
	for(e = &l.e[0]; e < &l.e[Ntypes]; e++){
		/*
		 *  check for open, the right type, and flow control
		 */
		if(e->q==0 || (t!=e->type && e->type!=-1) || e->q->next->len>Streamhi)
			continue;

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

		if(!waserror()){
			bp = allocb(len);
			memmove(bp->rptr, (uchar *)p, len);
			bp->wptr += len;
			bp->flags |= S_DELIM;
			PUTNEXT(e->q, bp);
			poperror();
		}
	}
}

/*
 *  input process, awakened on each interrupt with rcv buffers filled
 */
static int
isinput(void *arg)
{
	Msg *m = arg;

	return ((MPus(m->flags) & LANCEOWNER)==0) || l.wedged || l.self.first
		|| l.closeline;
}

static void
lancekproc(void *arg)
{
	Etherpkt *p;
	Ethertype *e;
	int len;
	int t;
	Lancemem *lm = LANCEMEM;
	Msg *m;
	Block *bp;

	USED(arg);

	while(waserror())
		print("lancekproc err %s\n", u->error);

	for(;;){
		qlock(&l.rlock);
		while(bp = getq(&l.self)){
			lanceup((Etherpkt*)bp->rptr, BLEN(bp));
			freeb(bp);
		}
		m = &(lm->rmr[l.rl]);
		while((MPus(m->flags) & LANCEOWNER)==0){
			l.inpackets++;
			t = MPus(m->flags);
			if(t & ERR){
				if(t & FRAM)
					l.frames++;
				if(t & OFLO)
					l.overflows++;
				if(t & CRC)
					l.crcs++;
				if(t & BUFF)
					l.buffs++;
			} else {
				/*
				 *  stuff packet up each queue that wants it
				 */
				p = &l.rp[l.rl];
				len = MPus(m->cntflags) - 4;
				lanceup(p, len);
			}

			/*
			 *  stage the next input buffer
			 */
			MPs(m->size) = -sizeof(Etherpkt);
			MPus(m->cntflags) = 0;
			MPus(m->laddr) = LADDR(&l.lrp[l.rl]);
			MPus(m->flags) = LANCEOWNER|HADDR(&l.lrp[l.rl]);
			wbflush();
			l.rl = RSUCC(l.rl);
			m = &(lm->rmr[l.rl]);
		}
		qunlock(&l.rlock);
		sleep(&l.rr, isinput, m);

		/*
		 *  if the lance is wedged, restart it
		 */
		if(l.wedged){
			print("lance wedged, restarting\n");
			lancestart(0);
		}

		/*
		 *  close ethertypes requesting it
		 */
		if(l.closeline){
			lock(&l.closepin);
			for(e = l.closeline; e; e = e->closeline){
				e->q = 0;
				wakeup(&e->rc);
			}
			l.closeline = 0;
			unlock(&l.closepin);
		}
	}
}

unix.superglobalmegacorp.com

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