V10/lsys/io/ni1010a.c
/*
* Interlan NI1010A Ethernet interface.
* Each hardware device has eight minor devices starting at N*8;
* the different channels may be caused to receive different
* Ethernet protocols via ENIOTYPE.
* Packets read or written have the Ethernet header in front.
*/
#include "sys/param.h"
#include "sys/stream.h"
#include "sys/conf.h"
#include "sys/ubaddr.h"
#include "sys/enio.h"
#include "sys/ethernet.h"
#include "sys/ni1010a.h"
/*
* hardware registers
*/
struct ildevice {
unsigned short il_csr;
short il_bar;
short il_bcr;
};
/*
* il_csr
*/
#define IL_EUA 0xc000 /* buffer address high bits */
#define IL_CDONE 0x0080 /* command done */
#define IL_CIE 0x0040 /* command intr enable */
#define IL_RDONE 0x0020 /* receive data arrived */
#define IL_RIE 0x0010 /* receive intr enable */
#define IL_STATUS 0x000f /* mask for command status */
/*
* commands, already shifted into place
*/
#define ILC_ONLINE 0x0900 /* speak to the network */
#define ILC_STAT 0x1800 /* return statistics and address */
#define ILC_RCV 0x2000 /* here's a receive buffer */
#define ILC_LDXMIT 0x2800 /* take this data */
#define ILC_XMIT 0x2900 /* take this data, start sending */
#define ILC_RESET 0x3f00 /* reset */
/*
* status values (some)
*/
#define ILERR_SUCCESS 0 /* ok */
#define ILERR_RETRIES 1 /* ok, but retried */
/* other values mean it didn't work */
/*
* status after diagnostics
*/
#define ILDIAG_SUCCESS 0 /* ok */
/*
* frame status bits in received packet
*/
#define FS_LOST 04 /* some earlier packet was lost */
#define FS_ALIGN 02 /* alignment error */
#define FS_CRC 01 /* CRC error */
#define CRCSIZE 4 /* size of the pointless CRC on received pkt */
#define MAXRBUFS 16 /* max number of rcv buffers allowed */
/*
* enormous statistics record
* we get it only for the ethernet address address
*/
struct il_stats {
short ils_fill1;
short ils_length; /* Length (should be 62) */
char ils_addr[6]; /* Ethernet Address */
short ils_frames; /* Number of Frames Received */
short ils_rfifo; /* Number of Frames in Receive FIFO */
short ils_xmit; /* Number of Frames Transmitted */
short ils_xcollis; /* Number of Excess Collisions */
short ils_frag; /* Number of Fragments Received */
short ils_lost; /* Number of Times Frames Lost */
short ils_multi; /* Number of Multicasts Accepted */
short ils_rmulti; /* Number of Multicasts Rejected */
short ils_crc; /* Number of CRC Errors */
short ils_align; /* Number of Alignment Errors */
short ils_collis; /* Number of Collisions */
short ils_owcollis; /* Number of Out-of-window Collisions */
short ils_fill2[8];
char ils_module[8]; /* Module ID */
char ils_firmware[8]; /* Firmware ID */
};
extern int ilcnt;
extern struct il il[];
extern struct ubaddr iladdr[];
/*
* il.flags
*/
#define CMDACT 01 /* some command active -- can't issue another yet */
#define INITDONE 02 /* finished init */
#define INITING 04 /* halfway through init; just wakeup on intr */
#define NEXTCH(i) (((i)+1)%NILCHAN)
#define ETHERMAXTU 1500 /* max packet size */
#define ILRBYTES (ETHERMAXTU*2) /* desired receive buffer size */
#define ILRSIZE 1024 /* preferred receive block size */
#define ISCHAIN(l) (((l)&07) == 0)
#define MKCHAIN(l) ((l)&~07) /* force length to allow rbuf chaining */
#define MKTRUNC(l) (MKCHAIN(l)-2) /* force to disallow */
long ilopen();
int ilclose(), ilput();
static struct qinit ilrinit = { noput, NULL, ilopen, ilclose, 0, 0 };
static struct qinit ilwinit = { ilput, NULL, ilopen, ilclose, 4*ETHERMAXTU, 64 };
static struct streamtab ilsinfo = { &ilrinit, &ilwinit };
struct cdevsw ilcdev = cstrinit(&ilsinfo);
long
ilopen(q, dev)
register struct queue *q;
register dev_t dev;
{
register struct ilchan *cp;
register struct il *is;
int unit;
dev = minor(dev);
unit = dev / NILCHAN;
if (unit >= ilcnt || ilinit(unit) == 0)
return(0);
is = &il[unit];
cp = &is->chan[dev%NILCHAN];
if(cp->rq)
return(0);
cp->rq = q;
q->ptr = (caddr_t)cp;
WR(q)->ptr = (caddr_t)cp;
WR(q)->flag |= QDELIM|QBIGB;
q->flag |= QDELIM;
cp->unit = unit; /* needed? */
cp->type = 0;
return(1);
}
/*
* init the hardware
*/
static struct il_stats ilstats;
static char ilsbusy;
ilinit(dev)
int dev;
{
register struct il *is;
register struct ildevice *addr;
ubm_t ubm;
uaddr_t uad;
register int sts;
int s;
is = &il[dev];
s = spl6();
while (is->flags & INITING)
tsleep((caddr_t)is, PZERO+1, 5);
splx(s);
if (is->flags & INITDONE)
return (1);
is->flags |= INITING;
if ((addr = (struct ildevice *)ubaddr(&iladdr[dev])) == NULL
|| badaddr(&addr->il_csr, sizeof(short))) {
printf("ni1010a %d absent\n", dev);
is->flags &=~ INITING;
return (0);
}
is->ubno = iladdr[dev].ubno;
is->addr = addr;
if ((sts = ilincmd(is, ILC_RESET)) == 0
|| (sts & IL_STATUS) != ILDIAG_SUCCESS) {
printf("ni1010a %d: reset failed csr %x\n", dev, is->lastcsr);
is->flags &=~ INITING;
return (0);
}
s = spl6();
while (ilsbusy)
tsleep((caddr_t)&ilsbusy, PZERO, 5);
ilsbusy = 1;
splx(s);
ubm = ubmalloc(is->ubno, sizeof(ilstats), USLP);
uad = ubmaddr(is->ubno, (char *)&ilstats, sizeof(ilstats), ubm);
addr->il_bar = uad;
addr->il_bcr = sizeof(ilstats);
sts = ilincmd(is, (int)((uad >> 2) & IL_EUA) | ILC_STAT);
ubmfree(is->ubno, ubm);
if (sts == 0 || (sts & IL_STATUS) != ILERR_SUCCESS) {
ilsbusy = 0;
wakeup((caddr_t)&ilsbusy);
printf("ni1010a %d: stat failed csr %x\n", dev, is->lastcsr);
is->flags &=~ INITING;
return (0);
}
bcopy(ilstats.ils_addr, is->enaddr, sizeof(is->enaddr));
ilsbusy = 0;
wakeup((caddr_t)&ilsbusy);
if ((sts = ilincmd(is, ILC_ONLINE)) == 0
|| (sts & IL_STATUS) != ILERR_SUCCESS) {
printf("ni1010a %d: online failed csr %x\n", dev, sts);
is->flags &=~ INITING;
return (0);
}
is->flags &=~ INITING;
is->flags |= INITDONE;
wakeup((caddr_t)is); /* in case someone is waiting */
s = spl6();
ilrcvbufs(is);
splx(s);
return (1);
}
int
ilincmd(is, csr)
register struct il *is;
register short csr;
{
register int s;
s = spl6();
is->flags |= CMDACT;
is->addr->il_csr = csr|IL_CIE;
while (is->flags & CMDACT)
if (tsleep((caddr_t)is, PZERO, 5) != TS_OK) {
is->flags &=~ CMDACT;
splx(s);
return (0);
}
csr = is->lastcsr; /* probably unnecessary */
splx(s);
return (csr);
}
ilclose(q)
struct queue *q;
{
struct ilchan *cp;
cp = (struct ilchan *)q->ptr;
cp->rq = 0;
}
/*
* stash data
*/
ilput(q, bp)
struct queue *q;
register struct block *bp;
{
register struct ilchan *cp;
register int s;
cp = (struct ilchan *)q->ptr;
switch (bp->type) {
case M_DATA:
cp->xlast = bp;
putq(q, bp);
if (bp->class&S_DELIM)
break;
return;
case M_IOCTL:
ilioctl(q, bp);
return;
default:
freeb(bp);
return;
}
/*
* S_DELIM: end of packet
*/
if (cp->xlast == NULL) /* empty record */
return;
cp->xlast = NULL;
cp->ndelims++;
s = spl6();
ilsendpkt(&il[cp->unit]);
splx(s);
}
/*
* pick a channel with a packet to send, and start sending it
* first block goes to the device here;
* interrupt code will feed it more
* adjust ethernet header in first block --
* interlan expects no source address
*/
ilsendpkt(is)
register struct il *is;
{
register struct ilchan *cp;
register struct block *bp;
register int i;
register struct etherpup *ep;
if (is->flags & CMDACT)
return;
again:
for (i = NEXTCH(is->lastch); ; i = NEXTCH(i)) {
cp = &is->chan[i];
if (cp->ndelims)
break;
if (i == is->lastch)
return;
}
is->lastch = i;
if ((bp = getq(WR(cp->rq))) == NULL)
panic("ilsendpkt");
cp->ndelims--;
if (bp->wptr - bp->rptr < sizeof(struct etherpup)) {
/* should probably try to pull packets together here */
printf("ni1010a %d: short header\n", cp->unit);
for (; (bp && bp->class&S_DELIM)==0; bp = getq(WR(cp->rq)))
freeb(bp);
goto again;
}
ep = (struct etherpup *)(bp->rptr);
bcopy(ep->dhost, ep->shost, sizeof(ep->dhost));
bp->rptr += sizeof(ep->dhost);
cp->xcur = bp;
ildebug(bp, 1, 0); /* how can we generate length cheaply? */
ilsendblock(is, bp);
}
/*
* command done interrupt
* if in init code, just wakeup
* if need receive buffers, make one
* else finish any pending transmit
*/
il1int(unit)
{
register struct il *is;
register struct ilchan *cp;
register int type;
is = &il[unit];
if (is->addr == NULL) {
printf("ni1010a %d: spurious interrupt\n", unit);
return;
}
is->lastcsr = is->addr->il_csr;
if ((is->flags & CMDACT) == 0) {
printf("ni1010a %d: stray cmd interrupt, csr %x\n", unit, is->lastcsr);
return;
}
is->flags &=~ CMDACT;
if (is->flags & INITING) {
wakeup((caddr_t)is);
return;
}
if (is->rbytes < ILRBYTES)
if (ilrcvbufs(is))
return;
cp = &is->chan[is->lastch];
if (cp->xcur) { /* sending something */
type = cp->xcur->class;
freeb(cp->xcur);
cp->xcur = NULL;
if ((type&S_DELIM)==0
&& (cp->xcur = getq(WR(cp->rq))) != NULL) {
ilsendblock(is, cp->xcur);
return;
}
switch (is->lastcsr & IL_STATUS) {
case ILERR_RETRIES:
is->collisions++;
case ILERR_SUCCESS:
is->opackets++;
break;
default:
is->oerrors++;
printf("ni1010a %d: xmt error 0x%x\n", unit, is->lastcsr&IL_STATUS);
break;
}
}
ilsendpkt(is);
}
/*
* feed a block to the controller
*/
ilsendblock(is, bp)
struct il *is;
register struct block *bp;
{
register struct ildevice *addr;
uaddr_t uad;
register int i;
addr = is->addr;
uad = ubadrptr(is->ubno, bp, ubmblk(is->ubno, bp, 0));
addr->il_bar = uad;
addr->il_bcr = bp->wptr - bp->rptr;
is->flags |= CMDACT;
i = ((uad>>2)&IL_EUA)|IL_RIE|IL_CIE;
if ((bp->class&S_DELIM)==0) /* not last piece of packet */
addr->il_csr = i | ILC_LDXMIT;
else
addr->il_csr = i | ILC_XMIT;
}
/*
* add receive buffer space if needed
* -- let it be an ordinary command, interrupt when complete, for now.
* this is probably too slow
* returns nonzero if a command was started
*/
ilrcvbufs(is)
register struct il *is;
{
register struct block *bp;
register struct ildevice *addr;
register int len;
register uaddr_t uad;
if (is->flags & CMDACT)
return (0);
if (is->rbytes >= ILRBYTES || is->rbufs >= MAXRBUFS)
return (0);
if ((bp = allocb(ILRSIZE)) == NULL)
return (0);
bp->next = NULL;
if (is->rfirst == NULL)
is->rfirst = bp;
else
is->rlast->next = bp;
is->rlast = bp;
len = bp->lim - bp->wptr;
/* assume at least eight bytes in bp */
len = MKCHAIN(len);
is->rbytes += len;
bp->wptr = bp->rptr + len;
is->rbufs++;
*(long *)bp->rptr = 0x80818283; /* debuggery */
uad = ubadrptr(is->ubno, bp, ubmblk(is->ubno, bp, 0));
is->flags |= CMDACT;
addr = is->addr;
addr->il_bar = uad;
addr->il_bcr = len;
addr->il_csr = ((uad>>2)&IL_EUA)|ILC_RCV|IL_RIE|IL_CIE;
return (1);
}
/*
* receive done interrupt
* -- (is->rfirst, is->rlast) is the set of buffers involved in receiving
* is->rnext, if non-empty, points to the next one we expect data in
* hence (is->rfirst, is->rnext) is the current incomplete packet
* is->rcur is the number of bytes not yet received in the current packet
*/
il0int(unit)
int unit;
{
register struct il *is;
register struct block *bp;
register struct block *lbp;
register struct ilchan *cp;
register struct queue *nq;
register int i;
int proto;
is = &il[unit];
if (is->addr)
is->lastcsr = is->addr->il_csr;
if (is->addr == NULL || is->rfirst == NULL) {
printf("ni1010a %d: spurious rcv intr csr %x\n", unit, is->lastcsr);
return;
}
if ((lbp = is->rnext) == NULL) {
lbp = is->rfirst;
lbp->rptr += 2; /* frame status, junk */
is->rcur = *lbp->rptr++;
is->rcur += *lbp->rptr++ << 8;
/* assume header is all in first block */
ildebug(lbp, 0, is->rcur - CRCSIZE);
}
is->rbytes -= lbp->wptr - lbp->base; /* sic -- in case first block */
is->rbufs--;
ilrcvbufs(is);
i = lbp->wptr - lbp->rptr;
if (is->rcur < i)
i = is->rcur;
lbp->wptr = lbp->rptr + i;
is->rcur -= i;
if (is->rcur > 0) { /* more expected */
if ((is->rnext = lbp->next) == NULL)
panic("il0int"); /* too draconian */
return;
}
/*
* complete packet:
* first block is is->rfirst,
* last is is->rnext == lbp
*/
lbp->wptr -= CRCSIZE; /* discard boring CRC */
lbp->class |= S_DELIM; /* make delimiter */
if (lbp->wptr < lbp->rptr) {
i = lbp->rptr - lbp->wptr;
lbp->rptr = lbp->wptr;
for (bp = is->rfirst; bp != lbp; bp = bp->next)
if (bp->next == lbp) {
bp->wptr -= i; /* and assume it stops here */
break;
}
}
bp = is->rfirst; /* first piece of this packet */
if (bp->rptr[-4] & (FS_ALIGN|FS_CRC)) { /* [-4] == frame status */
is->ierrors++;
cp = NULL;
} else {
is->ipackets++;
if (bp->rptr[-4] & FS_LOST)
is->ilost++;
/* assume the whole ethernet header is in the first block */
proto = ((struct etherpup *)(bp->rptr))->type;
for (i = 0; i < NILCHAN; i++) {
cp = &is->chan[i];
if (cp->rq && cp->type == proto)
break;
}
if (i >= NILCHAN)
cp = NULL;
}
lbp = lbp->next; /* one past the end */
if (cp == NULL || cp->rq->next->flag & QFULL) {
while (is->rfirst != lbp) {
bp = is->rfirst;
is->rfirst = bp->next;
freeb(bp);
}
} else {
nq = cp->rq->next;
while (is->rfirst != lbp) {
bp = is->rfirst;
is->rfirst = bp->next;
(*nq->qinfo->putp)(nq, bp);
}
}
is->rnext = NULL;
}
ilioctl(q, bp)
register struct queue *q;
register struct block *bp;
{
register struct ilchan *cp;
cp = (struct ilchan *)q->ptr;
bp->type = M_IOCACK;
switch(stiocom(bp)){
case ENIOTYPE:
cp->type = *((int *)stiodata(bp));
break;
case ENIOADDR:
bcopy(il[cp->unit].enaddr, stiodata(bp), ETHERALEN);
bp->wptr = bp->rptr + ETHERALEN + STIOCHDR;
break;
default:
bp->type = M_IOCNAK;
break;
}
qreply(q, bp);
}
#define ILLDEBSIZE 64
struct ild {
time_t time;
unsigned short code;
struct etherpup pup;
short len;
} ild[ILLDEBSIZE];
int ili = 0;
#include "sys/systm.h" /* just for time */
ildebug(bp, code, len)
struct block *bp;
int code, len;
{
register struct ild *ip;
ip = &ild[ili];
ip->time = time;
ip->code = code;
ip->len = len;
bcopy(bp->rptr, &ip->pup, sizeof(struct etherpup));
if (++ili >= ILLDEBSIZE)
ili = 0;
}