/* * Interlan Ethernet Communications Controller interface. * Provides fairly raw access to a number of Interlan controllers. * Minor device N talks to unit N / 8. Each written record should * be an interlan output packet - 6 bytes addr, 2 bytes type, data. * An ethernet packet type may be associated with a minor device with * the ENIOTYPE ioctl; input packets of that type will be sent to * the minor device in question. Input packets include 6 bytes src addr, * 6 bytes dest, 2 bytes type, and data. * * The physical address of a controller may be fetched with the ENIOADDR * ioctl on a minor device associated with that unit. */ #include "il.h" #if NIL > 0 #include "../h/param.h" #include "../h/systm.h" #include "../h/stream.h" #include "../h/map.h" #include "../h/buf.h" #include "../h/ubavar.h" #include "../h/conf.h" #include "../h/ioctl.h" #include "../h/ttyld.h" #include "../h/ill_reg.h" #include "../h/ethernet.h" int ilprobe(), ilattach(), ilrint(), ilcint(); struct uba_device *ilinfo[NIL]; u_short ilstd[] = { 0 }; struct uba_driver ildriver = { ilprobe, 0, ilattach, 0, ilstd, "il", ilinfo }; #define ILOUTSTANDING 1 /* max # of rcv bufs in controller q */ #define BOGUS 1 /* CDONE doesn't work */ struct il { /* per controller */ int attached; int active; int rcvpending; int ipackets, opackets; int ierrors, oerrors; int collisions; struct block *bp; /* waiting to be filled */ struct block *nbp; /* next block to be filled in this packet */ int len; /* amount left in this packet */ struct queue *tq; /* current transmit q */ struct block *freebp; unsigned char addr[6]; } il[NIL]; extern u_char blkdata[]; /* stream.c */ extern long blkubad; int ilprintfs = 0; struct il_stats ilstats; #define CHANS_PER_UNIT 8 #define NILCHAN (CHANS_PER_UNIT * NIL) struct ilchan{ /* per stream */ int unit; int packets; /* # of packets on q to output */ struct queue *rq; int type; /* ethernet protocol # */ int haveheader; /* ethernet header has gone by */ } ilchan[NILCHAN]; ilprobe(reg) caddr_t reg; { register int br, cvec; /* r11, r10 */ register struct ildevice *addr = (struct ildevice *) reg; register i; #ifdef lint br = 0; cvec = br; br = cvec; i = br; br = i; #endif addr->il_csr = ILC_OFFLINE|IL_CIE; DELAY(100000); i = addr->il_csr; /* clear CDONE */ if(cvec > 0 && cvec != 0x200) cvec -= 4; return(1); } ilattach(ui) struct uba_device *ui; { register struct il *is = &il[ui->ui_unit]; register struct ildevice *addr = (struct ildevice *)ui->ui_addr; int ubaddr, s; s = spl6(); addr->il_csr = ILC_RESET; while((addr->il_csr&IL_CDONE) == 0) ; if(addr->il_csr&IL_STATUS) printf("il%d: reset failed, csr=%b\n", ui->ui_unit, addr->il_csr, IL_BITS); ubaddr = uballoc(ui->ui_ubanum, (caddr_t)&ilstats, sizeof(ilstats), 0); if(ubaddr == 0){ printf(" no uballoc\n"); goto nostats; } addr->il_bar = ubaddr&0xffff; addr->il_bcr = sizeof(ilstats); addr->il_csr = ((ubaddr >> 2) & IL_EUA) | ILC_STAT; while((addr->il_csr&IL_CDONE) == 0) ; ubarelse(ui->ui_ubanum, &ubaddr); bcopy(ilstats.ils_addr, is->addr, sizeof(is->addr)); nostats: addr->il_csr = ILC_ONLINE; while((addr->il_csr&IL_CDONE) == 0) ; /* * ask ilcint to set up the first rcv buffer, * since the block stuff doesn't seem to be * initialized yet. */ is->rcvpending = ETHERMTU; is->active = 0; is->attached = 1; splx(s); } ilstart(unit) { int ubaddr, count; struct uba_device *ui = ilinfo[unit]; register struct il *is = &il[unit]; register struct ildevice *addr; struct ilchan *icp; struct block *bp; register struct queue *q; if(is->active){ printf(" start but active\n", unit); is->active = 0; } for(icp = ilchan; icp < &ilchan[NILCHAN]; icp++) if(icp->rq && icp->unit == unit && icp->packets > 0) break; if(icp >= &ilchan[NILCHAN]) return; q = WR(icp->rq); is->active = 1; addr = (struct ildevice *)ui->ui_addr; #ifdef BOGUS is->tq = q; bp = getq(q); if(bp == 0){ printf("ilstart no bp\n"); is->active = 0; return; } ildebug(bp, 1); ubaddr = iladdr((caddr_t)(bp->rptr)); addr->il_bar = ubaddr&0xffff; addr->il_bcr = bp->wptr - bp->rptr; addr->il_csr = ((ubaddr>>2)&IL_EUA)|ILC_LDXMIT|IL_RIE|IL_CIE; is->freebp = bp; /* * I would have given the entire packet to the controller, * but it's brain-damaged. So I wait for the interrupt to * put the next one on. */ #else while(bp = getq(q)){ if(bp->type != M_DATA){ /* don't free, it's re-used */ break; } ubaddr = iladdr((caddr_t)bp->rptr); addr->il_bar = ubaddr&0xffff; addr->il_bcr = bp->wptr - bp->rptr; addr->il_csr = ((ubaddr>>2)&IL_EUA)|ILC_LDXMIT|IL_RIE; ilcdone(addr, "LDX"); freeb(bp); } if(bp == 0){ printf("ilstart no delim?\n"); bp = allocb(1); } bp->wptr = bp->rptr; --(icp->packets); ubaddr = iladdr((caddr_t)bp->base); addr->il_bar = ubaddr&0xffff; addr->il_bcr = 0; addr->il_csr = ((ubaddr>>2)&IL_EUA)|ILC_XMIT|IL_CIE|IL_RIE; /* go, baby */ is->opackets++; freeb(bp); #endif BOGUS } /* * transmit done */ ilcint(unit) { int ubaddr; register struct il *is = &il[unit]; struct uba_device *ui = ilinfo[unit]; register struct ildevice *addr = (struct ildevice *)ui->ui_addr; short csr; register struct block *bp; if(is->active == 0){ printf("il%d: stray xmit interrupt, csr=%b\n", unit, addr->il_csr, IL_BITS); return; } #ifdef BOGUS if(is->tq){ if(is->freebp) freeb(is->freebp); is->freebp = 0; if((bp = getq(is->tq)) == 0){ printf("gak\n"); is->active = 0; is->tq = 0; return; } ubaddr = iladdr((caddr_t)(bp->rptr)); addr->il_bar = ubaddr&0xffff; addr->il_bcr = bp->wptr - bp->rptr; if(bp->type == M_DELIM){ addr->il_csr = ((ubaddr>>2)&IL_EUA)|ILC_XMIT|IL_RIE|IL_CIE; ((struct ilchan *)(is->tq->ptr))->packets -= 1; is->tq = 0; freeb(bp); } else { addr->il_csr = ((ubaddr>>2)&IL_EUA)|ILC_LDXMIT|IL_RIE|IL_CIE; is->freebp = bp; } return; } #endif BOGUS csr = addr->il_csr; is->active = 0; if(is->rcvpending) ilsetup(is, addr, is->rcvpending); /* check status of last xmit */ csr &= IL_STATUS; if(csr > ILERR_RETRIES){ printf("il%d: tx error 0x%x\n", unit, csr); is->oerrors++; } else if(csr > ILERR_SUCCESS){ is->collisions++; } #ifdef BOGUS else is->opackets++; #endif ilstart(unit); } ilrint(unit) { register struct il *is = &il[unit]; register struct ildevice *addr = (struct ildevice *)ilinfo[unit]->ui_addr; register struct il_rheader *hp; int len; struct block *bp, *bp1; register struct ilchan *icp; register struct queue *q; if(is->bp == 0) panic("ilrint no is->bp"); if(is->nbp == 0) panic("ilrint no is->nbp"); bp = is->nbp; if(bp == is->bp){ /* first buffer of a packet */ hp = (struct il_rheader *)bp->rptr; is->len = hp->ilr_length; is->len += 4; } is->len -= (bp->wptr - bp->rptr); is->nbp = bp->next; /* ilsetup will take care of huge packets by asking * the controller to truncate. */ if(is->len <= 0) goto done; if((bp->wptr - bp->rptr) % 8) /* not chaining */ goto done; if(is->nbp == 0){ /* more, more */ if(is->active){ is->rcvpending = is->len; } else { ilsetup(is, addr, is->len); } } return; done: bp = is->bp; hp = (struct il_rheader *)bp->rptr; len = hp->ilr_length - sizeof(struct il_rheader); if(len < 46 || len > ETHERMTU || is->len > 0){ if(ilprintfs) printf("il%d: ilr_length %d is-len %d\n", unit, hp->ilr_length, is->len); is->ierrors++; goto setup; } if(hp->ilr_status&ILFSTAT_L) is->ierrors++; len += sizeof(struct il_rheader) - 4; for(icp = ilchan; icp < &ilchan[NILCHAN]; icp++) if(icp->rq && icp->unit == unit && icp->type == hp->ilr_type) break; if(icp >= &ilchan[NILCHAN]){ if(ilprintfs) printf("type %x?\n", hp->ilr_type); goto setup; } q = icp->rq; if(q->next->flag&QFULL){ if(ilprintfs) printf(" q full\n"); is->ierrors++; goto setup; } bp->rptr = &(hp->ilr_dhost[0]); ildebug(bp, 0); len = hp->ilr_length - 4; while(bp && bp != is->nbp){ if (bp->wptr - bp->rptr > len) bp->wptr = bp->rptr + len; len -= bp->wptr - bp->rptr; bp1 = bp->next; if (bp->wptr > bp->rptr) (*q->next->qinfo->putp)(q->next, bp); else freeb(bp); bp = bp1; } is->bp = is->nbp; bp = allocb(1); if(bp){ bp->type = M_DELIM; (*q->next->qinfo->putp)(q->next, bp); is->ipackets++; } else { printf("ilrint no DELIM bp\n"); } setup: /* free up blocks in a rejected packet */ bp = is->bp; while(bp && bp != is->nbp){ bp1 = bp->next; freeb(bp); bp = bp1; } is->bp = bp; if(is->active){ /* don't interfere w/ output */ is->rcvpending = ETHERMTU; return; } ilsetup(is, addr, ETHERMTU); } iladdr(memaddr) caddr_t memaddr; { register long a; if((unsigned)memaddr < (unsigned)blkdata){ printf("memaddr %x blkdata %x\n", memaddr, blkdata); panic("iladdr"); } a = (long)memaddr - (long)(caddr_t)blkdata + ((long)blkubad&0x03ffff); /* sure */ return(a); } int nodev(), ilopen(), ilclose(), ilput(); struct qinit ilrinit = { nodev, NULL, ilopen, ilclose, 0, 0 }; struct qinit ilwinit = { ilput, NULL, ilopen, ilclose, ETHERMTU-6, 64 }; struct streamtab ilsinfo = { &ilrinit, &ilwinit }; ilopen(q, dev) register struct queue *q; register dev_t dev; { register struct ilchan *icp; register struct il *is; int unit, s; dev = minor(dev); unit = dev / CHANS_PER_UNIT; if(dev >= NILCHAN) return(0); if(unit >= NIL) return(0); is = &il[unit]; if(is->attached == 0) return(0); icp = &ilchan[dev]; if(icp->rq) return(0); icp->rq = q; q->ptr = (caddr_t)icp; WR(q)->ptr = (caddr_t)icp; WR(q)->flag |= QDELIM|QBIGB; q->flag |= QDELIM; icp->unit = unit; icp->type = 0; icp->packets = 0; s = spl6(); if(is->rcvpending && is->active == 0){ /* first open, supply rcv buffer */ is->active = 1; ilcint(unit); } splx(s); return(1); } ilclose(q) register struct queue *q; { register struct ilchan *icp; icp = (struct ilchan *)q->ptr; icp->rq = 0; icp->packets = 0; } /* * Ilput expects the first block of each packet to contain a six byte * ethernet address followed by a two byte packet type number in * network byte order. */ ilput(q, bp) register struct queue *q; struct block *bp; { register struct il *is; int unit, s; register struct ilchan *icp; icp = (struct ilchan *)q->ptr; unit = icp->unit; if(bp->type == M_DATA){ if (!icp->haveheader) { icp->haveheader = 1; ilfixheader(bp); } putq(q, bp); return; } else if(bp->type == M_IOCTL){ ilioctl(q, bp); return; } else if(bp->type != M_DELIM){ freeb(bp); return; } /* have end of packet */ icp->haveheader = 0; putq(q, bp); is = &il[unit]; s = spl6(); icp->packets++; if(is->active == 0) ilstart(unit); splx(s); } /* * Interlan drivers accept a strange type of ethernet header. * It is the normal ethernet header with the source field removed. */ ilfixheader(bp) register struct block *bp; { register struct ether_in *ep; if (bp->wptr - bp->rptr < sizeof(struct ether_out)) { printf("ether_header too short\n"); return; } ep = (struct ether_in *)(bp->rptr); bcopy(ep->dhost, ep->shost, 6); bp->rptr += 6; } #define ILLDEBSIZE 64 struct { time_t time; unsigned short code; unsigned char addr[6]; } illdebarr[ILLDEBSIZE]; int illindex = 0; ildebug(bp, code) register struct block *bp; { illdebarr[illindex].time = time; illdebarr[illindex].code = code; bcopy(bp->rptr, illdebarr[illindex].addr, 6); illindex = (illindex + 1) % ILLDEBSIZE; } ilioctl(q, bp) register struct queue *q; register struct block *bp; { union stmsg *sp; register struct ilchan *icp; int unit; struct uba_device *ui; struct ildevice *addr; icp = (struct ilchan *)q->ptr; sp = (union stmsg *)bp->rptr; bp->type = M_IOCACK; switch(sp->ioc0.com){ case ENIOTYPE: icp->type = *((int *)(sp->iocx.xxx)); break; case ENIOADDR: bcopy(il[icp->unit].addr, sp->iocx.xxx, 6); break; case ENIOCMD: /* perform a non-dma interlan command */ unit = icp->unit; ui = ilinfo[unit]; addr = (struct ildevice *)ui->ui_addr; addr->il_csr = *((int *)(sp->iocx.xxx)); *((int *)(sp->iocx.xxx)) = ilcdone(addr, "CMD"); break; default: bp->type = M_IOCNAK; break; } qreply(q, bp); } ilsetup(is, addr, n) register struct il *is; register struct ildevice *addr; { register struct block *bp; int ubaddr, count; if(is->active) panic("ilsetup active"); /* * is->bp is start of buffer chain, is->nbp is next buffer * to be DMA'd into. if is->bp is zero, there is no chain * yet, else if is->nbp is zero, add stuff to the end of * the chain, else only add stuff if we need to. */ if(is->nbp && is->bp == 0){ printf("il: nbp but no bp in ilsetup\n"); is->nbp = 0; } count = 0; /* count bytes and buffers already loaded into controller */ for(bp = is->nbp; bp; bp = bp->next){ n -= bp->wptr - bp->rptr; count++; } if(n <= 0) return; if(count > ILOUTSTANDING) printf("ilsetup: already %d in bar/bcr q!\n", count); if(is->bp == 0){ bp = is->nbp = 0; } else { for(bp = is->bp; bp->next; bp = bp->next) ; } /* now bp is 0 or else the last block in the chain */ while(count < ILOUTSTANDING && n > 0){ if(bp == 0){ is->bp = is->nbp = bp = allocb(n); } else { bp->next = allocb(n); bp = bp->next; } if(bp == 0){ is->rcvpending = n; return; } bp->next = 0; if(is->nbp == 0) is->nbp = bp; bp->rptr = bp->base; bp->wptr = bp->lim; if(n > ETHERMTU){ /* tell controller to truncate */ bp->wptr -= 2; n = 0; } n -= bp->wptr - bp->rptr; count++; ubaddr = iladdr((caddr_t)(bp->rptr)); addr->il_bar = ubaddr & 0xffff; addr->il_bcr = bp->wptr - bp->rptr; addr->il_csr = ((ubaddr>>2)&IL_EUA)|ILC_RCV|IL_RIE; ilcdone(addr, "RCV"); } is->rcvpending = 0; } ilcdone(addr, s) struct ildevice *addr; char *s; { int count; count = 0; while((addr->il_csr & IL_CDONE) == 0){ count++; if(count > 200000) break; } if(count > 200000){ printf("%s: addr 0%o csr 0x%x\n", s, addr, addr->il_csr); return(1); } return(addr->il_csr & 0xf); } #endif NIL