/* * National Semiconductor DP8390 * and SMC 83C90 * Network Interface Controller. */ #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" enum { Cr = 0x00, /* command register, all pages */ Stp = 0x01, /* stop */ Sta = 0x02, /* start */ Txp = 0x04, /* transmit packet */ RDMAread = (1<<3), /* remote DMA read */ RDMAwrite = (2<<3), /* remote DMA write */ RDMAsend = (3<<3), /* remote DMA send packet */ RDMAabort = (4<<3), /* abort/complete remote DMA */ Page0 = (0x00<<6), /* page select */ Page1 = (0x01<<6), Page2 = (0x02<<6), }; enum { /* Page 0, read */ Clda0 = 0x01, /* current local DMA address 0 */ Clda1 = 0x02, /* current local DMA address 1 */ Bnry = 0x03, /* boundary pointer (R/W) */ Tsr = 0x04, /* transmit status register */ Ncr = 0x05, /* number of collisions register */ Fifo = 0x06, /* FIFO */ Isr = 0x07, /* interrupt status register (R/W) */ Crda0 = 0x08, /* current remote DMA address 0 */ Crda1 = 0x08, /* current remote DMA address 1 */ Rsr = 0x0C, /* receive status register */ Cntr0 = 0x0D, /* frame alignment errors */ Cntr1 = 0x0E, /* CRC errors */ Cntr2 = 0x0F, /* missed packet errors */ }; enum { /* Page 0, write */ Pstart = 0x01, /* page start register */ Pstop = 0x02, /* page stop register */ Tpsr = 0x04, /* transmit page start address */ Tbcr0 = 0x05, /* transmit byte count register 0 */ Tbcr1 = 0x06, /* transmit byte count register 1 */ Rsar0 = 0x08, /* remote start address register 0 */ Rsar1 = 0x09, /* remote start address register 1 */ Rbcr0 = 0x0A, /* remote byte count register 0 */ Rbcr1 = 0x0B, /* remote byte count register 1 */ Rcr = 0x0C, /* receive configuration register */ Tcr = 0x0D, /* transmit configuration register */ Dcr = 0x0E, /* data configuration register */ Imr = 0x0F, /* interrupt mask */ }; enum { /* Page 1, read/write */ Par0 = 0x01, /* physical address register 0 */ Curr = 0x07, /* current page register */ Mar0 = 0x08, /* multicast address register 0 */ }; enum { /* Interrupt Status Register */ Prx = 0x01, /* packet received */ Ptx = 0x02, /* packet transmitted */ Rxe = 0x04, /* receive error */ Txe = 0x08, /* transmit error */ Ovw = 0x10, /* overwrite warning */ Cnt = 0x20, /* counter overflow */ Rdc = 0x40, /* remote DMA complete */ Rst = 0x80, /* reset status */ }; enum { /* Interrupt Mask Register */ Prxe = 0x01, /* packet received interrupt enable */ Ptxe = 0x02, /* packet transmitted interrupt enable */ Rxee = 0x04, /* receive error interrupt enable */ Txee = 0x08, /* transmit error interrupt enable */ Ovwe = 0x10, /* overwrite warning interrupt enable */ Cnte = 0x20, /* counter overflow interrupt enable */ Rdce = 0x40, /* DMA complete interrupt enable */ }; enum { /* Data Configuration register */ Wts = 0x01, /* word transfer select */ Bos = 0x02, /* byte order select */ Las = 0x04, /* long address select */ Ls = 0x08, /* loopback select */ Arm = 0x10, /* auto-initialise remote */ Ft1 = (0x00<<5), /* FIFO threshhold select 1 byte/word */ Ft2 = (0x01<<5), /* FIFO threshhold select 2 bytes/words */ Ft4 = (0x02<<5), /* FIFO threshhold select 4 bytes/words */ Ft6 = (0x03<<5), /* FIFO threshhold select 6 bytes/words */ }; enum { /* Transmit Configuration Register */ Crc = 0x01, /* inhibit CRC */ Lb = 0x02, /* internal loopback */ Atd = 0x08, /* auto transmit disable */ Ofst = 0x10, /* collision offset enable */ }; enum { /* Transmit Status Register */ Ptxok = 0x01, /* packet transmitted */ Col = 0x04, /* transmit collided */ Abt = 0x08, /* tranmit aborted */ Crs = 0x10, /* carrier sense lost */ Fu = 0x20, /* FIFO underrun */ Cdh = 0x40, /* CD heartbeat */ Owc = 0x80, /* out of window collision */ }; enum { /* Receive Configuration Register */ Sep = 0x01, /* save errored packets */ Ar = 0x02, /* accept runt packets */ Ab = 0x04, /* accept broadcast */ Am = 0x08, /* accept multicast */ Pro = 0x10, /* promiscuous physical */ Mon = 0x20, /* monitor mode */ }; enum { /* Receive Status Register */ Prxok = 0x01, /* packet received intact */ Crce = 0x02, /* CRC error */ Fae = 0x04, /* frame alignment error */ Fo = 0x08, /* FIFO overrun */ Mpa = 0x10, /* missed packet */ Phy = 0x20, /* physical/multicast address */ Dis = 0x40, /* receiver disabled */ Dfr = 0x80, /* deferring */ }; typedef struct { uchar status; uchar next; uchar len0; uchar len1; } Hdr; typedef struct { Hdr; uchar data[Dp8390BufSz-sizeof(Hdr)]; } Buf; static void dp8390disable(Ctlr *ctlr) { Board *board = ctlr->board; int timo; /* * Stop the chip. Set the Stp bit and wait for the chip * to finish whatever was on its tiny mind before it sets * the Rst bit. * We need the timeout because there may not be a real * chip there if this is called when probing for a device * at boot. */ outb(board->dp8390+Cr, Page0|RDMAabort|Stp); for(timo = 10000; (inb(board->dp8390+Isr) & Rst) == 0 && timo; timo--) ; } void dp8390reset(Ctlr *ctlr) { Board *board = ctlr->board; /* * This is the initialisation procedure described * as 'mandatory' in the datasheet. */ dp8390disable(ctlr); if(board->bit16) outb(board->dp8390+Dcr, Ft4|Ls|Wts); else outb(board->dp8390+Dcr, Ft4|Ls); outb(board->dp8390+Rbcr0, 0); outb(board->dp8390+Rbcr1, 0); outb(board->dp8390+Rcr, Ab); outb(board->dp8390+Tcr, Lb); outb(board->dp8390+Bnry, board->pstart); outb(board->dp8390+Pstart, board->pstart); outb(board->dp8390+Pstop, board->pstop); outb(board->dp8390+Isr, 0xFF); outb(board->dp8390+Imr, Ovwe|Txee|Rxee|Ptxe|Prxe); outb(board->dp8390+Cr, Page1|RDMAabort|Stp); /* * Can't initialise ethernet address as we may * not know it yet. */ outb(board->dp8390+Curr, board->pstart+1); /* * Leave the chip initialised, * but in internal loopback mode. */ outb(board->dp8390+Cr, Page0|RDMAabort|Sta); outb(board->dp8390+Tpsr, board->tstart); } void dp8390attach(Ctlr *ctlr) { /* * Enable the chip for transmit/receive. * The init routine leaves the chip in internal * loopback. */ outb(ctlr->board->dp8390+Tcr, 0); } void dp8390mode(Ctlr *ctlr, int on) { if(on) outb(ctlr->board->dp8390+Rcr, Pro|Ab); else outb(ctlr->board->dp8390+Rcr, Ab); } void dp8390setea(Ctlr *ctlr) { Board *board = ctlr->board; uchar cr; int i; /* * Set the ethernet address into the chip. * Take care to restore the command register * afterwards. We don't care about multicast * addresses as we never set the multicast * enable. */ cr = inb(board->dp8390+Cr); outb(board->dp8390+Cr, Page1|(~Page0 & cr)); for(i = 0; i < sizeof(ctlr->ea); i++) outb(board->dp8390+Par0+i, ctlr->ea[i]); outb(board->dp8390+Cr, cr); } void* dp8390read(Ctlr *ctlr, void *to, ulong from, ulong len) { Board *board = ctlr->board; uchar cr; int timo; /* * If the interface has shared memory, just * do a memmove. * In this case, 'from' is an index into the shared memory. */ if(board->ram){ memmove(to, (void*)(board->ramstart+from), len); return to; } /* * Read some data at offset 'from' in the board's memory * using the DP8390 remote DMA facility, and place it at * 'to' in main memory, via the I/O data port. */ cr = inb(board->dp8390+Cr); outb(board->dp8390+Cr, Page0|RDMAabort|Sta); outb(board->dp8390+Isr, Rdc); /* * Set up the remote DMA address and count. */ if(board->bit16) len = ROUNDUP(len, 2); outb(board->dp8390+Rbcr0, len & 0xFF); outb(board->dp8390+Rbcr1, (len>>8) & 0xFF); outb(board->dp8390+Rsar0, from & 0xFF); outb(board->dp8390+Rsar1, (from>>8) & 0xFF); /* * Start the remote DMA read and suck the data * out of the I/O port. */ outb(board->dp8390+Cr, Page0|RDMAread|Sta); if(board->bit16) inss(board->data, to, len/2); else insb(board->data, to, len); /* * Wait for the remote DMA to complete. The timeout * is necessary because we may call this routine on * a non-existent chip during initialisation and, due * to the miracles of the bus, we could get this far * and still be talking to a slot full of nothing. */ for(timo = 10000; (inb(board->dp8390+Isr) & Rdc) == 0 && timo; timo--) ; outb(board->dp8390+Isr, Rdc); outb(board->dp8390+Cr, cr); return to; } void* dp8390write(Ctlr *ctlr, ulong to, void *from, ulong len) { Board *board = ctlr->board; uchar cr; /* * If the interface has shared memory, just * do a memmove. * In this case, 'to' is an index into the shared memory. */ if(board->ram){ memmove((void*)(board->ramstart+to), from, len); return (void*)to; } /* * Write some data to offset 'to' in the board's memory * using the DP8390 remote DMA facility, reading it at * 'from' in main memory, via the I/O data port. */ cr = inb(board->dp8390+Cr); outb(board->dp8390+Cr, Page0|RDMAabort|Sta); outb(board->dp8390+Isr, Rdc); /* * Set up the remote DMA address and count. * This is straight from the datasheet, hence * the initial write to Rbcr0 and Cr. */ outb(board->dp8390+Rbcr0, 0xFF); outb(board->dp8390+Cr, Page0|RDMAread|Sta); if(board->bit16) len = ROUNDUP(len, 2); outb(board->dp8390+Rbcr0, len & 0xFF); outb(board->dp8390+Rbcr1, (len>>8) & 0xFF); outb(board->dp8390+Rsar0, to & 0xFF); outb(board->dp8390+Rsar1, (to>>8) & 0xFF); /* * Start the remote DMA write and pump the data * into the I/O port. */ outb(board->dp8390+Cr, Page0|RDMAwrite|Sta); if(board->bit16) outss(board->data, from, len/2); else outsb(board->data, from, len); /* * Wait for the remote DMA to finish. We'll need * a timeout here if this ever gets called before * we know there really is a chip there. */ while((inb(board->dp8390+Isr) & Rdc) == 0) ; outb(board->dp8390+Isr, Rdc); outb(board->dp8390+Cr, cr); return (void*)to; } void dp8390receive(Ctlr *ctlr) { Board *board = ctlr->board; RingBuf *ring; uchar bnry, curr, next; Hdr hdr; ulong data; int i, len; bnry = inb(board->dp8390+Bnry); next = bnry+1; if(next >= board->pstop) next = board->pstart; for(i = 0; ; i++){ outb(board->dp8390+Cr, Page1|RDMAabort|Sta); curr = inb(board->dp8390+Curr); outb(board->dp8390+Cr, Page0|RDMAabort|Sta); if(next == curr) break; /* * Hack to keep away from the card's memory while it is receiving * a packet. This is only a problem on the NCR 3170 Safari. * * We peek at the current local DMA registers and, if they are * changing, wait. */ if(strcmp(arch->id, "NCRD.0") == 0){ uchar a, b, c; for(;;delay(10)){ a = inb(board->dp8390+Clda0); b = inb(board->dp8390+Clda0); if(a != b) continue; c = inb(board->dp8390+Clda0); if(c != b) continue; break; } } ctlr->inpackets++; data = next*Dp8390BufSz; dp8390read(ctlr, &hdr, data, sizeof(Hdr)); len = ((hdr.len1<<8)|hdr.len0)-4; /* * Chip is badly scrogged, reinitialise it. * dp8390reset() calls the disable function. * There's no need to reload the ethernet * address. * * Untested. */ if(hdr.next < board->pstart || hdr.next >= board->pstop || len < 60){ print("scrogged\n"); dp8390reset(ctlr); dp8390attach(ctlr); return; } ring = &ctlr->rb[ctlr->ri]; if(ring->owner == Interface){ ring->len = len; data += sizeof(Hdr); if((data+len) >= board->pstop*Dp8390BufSz){ len = board->pstop*Dp8390BufSz - data; dp8390read(ctlr, ring->pkt+len, board->pstart*Dp8390BufSz, (data+ring->len) - board->pstop*Dp8390BufSz); } dp8390read(ctlr, ring->pkt, data, len); ring->owner = Host; ctlr->ri = NEXT(ctlr->ri, ctlr->nrb); } next = hdr.next; bnry = next-1; if(bnry < board->pstart) bnry = board->pstop-1; outb(board->dp8390+Bnry, bnry); } } void dp8390transmit(Ctlr *ctlr) { Board *board; RingBuf *ring; int s; s = splhi(); board = ctlr->board; ring = &ctlr->tb[ctlr->ti]; if(ring->busy == 0 && ring->owner == Interface){ dp8390write(ctlr, board->tstart*Dp8390BufSz, ring->pkt, ring->len); outb(board->dp8390+Tbcr0, ring->len & 0xFF); outb(board->dp8390+Tbcr1, (ring->len>>8) & 0xFF); outb(board->dp8390+Cr, Page0|RDMAabort|Txp|Sta); ring->busy = 1; } splx(s); } void dp8390intr(Ctlr *ctlr) { Board *board = ctlr->board; RingBuf *ring; uchar isr; /* * While there is something of interest, * clear all the interrupts and process. */ while(isr = inb(board->dp8390+Isr)){ outb(board->dp8390+Isr, isr); if(isr & Ovw){ /* * Need to do some work here: * stop the chip; * clear out the ring; * re-enable the chip. * The procedure to put the chip back on * an active network is to first put it * into internal loopback mode, enable it, * then take it out of loopback. * * Untested. */ ctlr->overflows++; dp8390disable(ctlr); (*ctlr->board->receive)(ctlr); outb(board->dp8390+Tcr, Lb); outb(board->dp8390+Cr, Page0|RDMAabort|Sta); dp8390attach(ctlr); } /* * We have received packets. * Take a spin round the ring and * wakeup the kproc. */ if(isr & (Rxe|Prx)){ (*ctlr->board->receive)(ctlr); wakeup(&ctlr->rr); } /* * Log errors and successful transmissions. */ if(isr & Txe) ctlr->oerrs++; if(isr & Rxe){ ctlr->frames += inb(board->dp8390+Cntr0); ctlr->crcs += inb(board->dp8390+Cntr1); ctlr->buffs += inb(board->dp8390+Cntr2); } if(isr & Ptx) ctlr->outpackets++; /* * A packet completed transmission, successfully or * not. Start transmission on the next buffered packet, * and wake the output routine. */ if(isr & (Txe|Ptx)){ ring = &ctlr->tb[ctlr->ti]; ring->owner = Host; ring->busy = 0; ctlr->ti = NEXT(ctlr->ti, ctlr->ntb); (*ctlr->board->transmit)(ctlr); wakeup(&ctlr->tr); } } }