diff options
Diffstat (limited to 'i386/i386at/gpl/if_ns.c')
-rw-r--r-- | i386/i386at/gpl/if_ns.c | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/i386/i386at/gpl/if_ns.c b/i386/i386at/gpl/if_ns.c new file mode 100644 index 0000000..da629cb --- /dev/null +++ b/i386/i386at/gpl/if_ns.c @@ -0,0 +1,642 @@ +/* + * Copyright (c) 1994 Shantanu Goel + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * THE AUTHOR ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" + * CONDITION. THE AUTHOR DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + */ + +/* + * Written 1992,1993 by Donald Becker. + * + * Copyright 1993 United States Government as represented by the + * Director, National Security Agency. This software may be used and + * distributed according to the terms of the GNU Public License, + * incorporated herein by reference. + * + * The Author may be reached as becker@super.org or + * C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715 + */ + +#include <ul.h> +#include <wd.h> +#include <hpp.h> +#if NUL > 0 || NWD > 0 || NHPP > 0 +/* + * Generic NS8390 routines. + * Derived from the Linux driver by Donald Becker. + * + * Shantanu Goel (goel@cs.columbia.edu) + */ +#include <sys/types.h> +#include <device/device_types.h> +#include <device/errno.h> +#include <device/io_req.h> +#include <device/if_hdr.h> +#include <device/if_ether.h> +#include <device/net_status.h> +#include <device/net_io.h> +#include <chips/busses.h> +#include <i386/machspl.h> +#include <i386/pio.h> +#include <i386at/gpl/if_nsreg.h> + +#define IO_DELAY __asm__ __volatile__ ("outb %al,$0x80") +#define outb_p(p, v) { outb(p, v); IO_DELAY; } +#define inb_p(p) ({ unsigned char _v; _v = inb(p); IO_DELAY; _v; }) + +#define NSDEBUG +#ifdef NSDEBUG +int nsdebug = 0; +#define DEBUGF(stmt) { if (nsdebug) stmt; } +#else +#define DEBUGF(stmt) +#endif + +void nsxint(struct nssoftc *); +void nsrint(struct nssoftc *); +void nsxmit(struct nssoftc *, unsigned, int); +void nsrxoverrun(struct nssoftc *); + +/* + * Initialize the NIC. + * Must be called at splimp(). + */ +void +nsinit(sc) + struct nssoftc *sc; +{ + int port = sc->sc_port, i, rxconfig; + int endcfg = sc->sc_word16 ? (0x48 | ENDCFG_WTS) : 0x48; + struct ifnet *ifp = &sc->sc_if; + + /* + * Reset the board. + */ + (*sc->sc_reset)(sc); + + sc->sc_oactive = 0; + sc->sc_txing = 0; + sc->sc_timer = 0; + sc->sc_tx1 = sc->sc_tx2 = 0; + sc->sc_curpg = sc->sc_rxstrtpg; + + /* + * Follow National Semiconductor's recommendations for + * initializing the DP83902. + */ + outb_p(port, E8390_NODMA+E8390_PAGE0+E8390_STOP); /* 0x21 */ + outb_p(port + EN0_DCFG, endcfg); /* 0x48 or 0x49 */ + + /* + * Clear remote byte count registers. + */ + outb_p(port + EN0_RCNTLO, 0); + outb_p(port + EN0_RCNTHI, 0); + + /* + * Set to monitor and loopback mode -- this is vital! + */ + outb_p(port + EN0_RXCR, E8390_RXOFF); /* 0x20 */ + outb_p(port + EN0_TXCR, E8390_TXOFF); /* 0x02 */ + + /* + * Set transmit page and receive ring. + */ + outb_p(port + EN0_TPSR, sc->sc_txstrtpg); + outb_p(port + EN0_STARTPG, sc->sc_rxstrtpg); + outb_p(port + EN0_BOUNDARY, sc->sc_stoppg - 1); + outb_p(port + EN0_STOPPG, sc->sc_stoppg); + + /* + * Clear pending interrupts and mask. + */ + outb_p(port + EN0_ISR, 0xff); + + /* + * Enable the following interrupts: receive/transmit complete, + * receive/transmit error, and Receiver OverWrite. + * + * Counter overflow and Remote DMA complete are *not* enabled. + */ + outb_p(port + EN0_IMR, ENISR_RX | ENISR_TX | ENISR_RX_ERR | + ENISR_TX_ERR | ENISR_OVER ); + + /* + * Copy station address into 8390 registers. + */ + outb_p(port, E8390_NODMA + E8390_PAGE1 + E8390_STOP); /* 0x61 */ + for (i = 0; i < ETHER_ADDR_LEN; i++) + outb_p(port + EN1_PHYS + i, sc->sc_addr[i]); + + /* + * Set up to accept all multicast packets. + */ + for (i = 0; i < 8; i++) + outb_p(port + EN1_MULT + i, 0xff); + + /* + * Initialize CURRent pointer + */ + outb_p(port + EN1_CURPAG, sc->sc_rxstrtpg); + + /* + * Program command register for page 0. + */ + outb_p(port, E8390_NODMA + E8390_PAGE0 + E8390_STOP); + +#if 0 + outb_p(port + EN0_ISR, 0xff); + outb_p(port + EN0_IMR, ENISR_ALL); +#endif + + outb_p(port + E8390_CMD, E8390_NODMA + E8390_PAGE0 + E8390_START); + outb_p(port + EN0_TXCR, E8390_TXCONFIG); /* xmit on */ + + /* 3c503 TechMan says rxconfig only after the NIC is started. */ + rxconfig = E8390_RXCONFIG; + if (ifp->if_flags & IFF_ALLMULTI) + rxconfig |= 0x08; + if (ifp->if_flags & IFF_PROMISC) + rxconfig |= 0x10; + outb_p(port + EN0_RXCR, rxconfig); /* rx on */ + + /* + * Mark interface as up and start output. + */ + ifp->if_flags |= IFF_RUNNING; + nsstart(sc); +} + +/* + * Start output on interface. + * Must be called at splimp(). + */ +void +nsstart(sc) + struct nssoftc *sc; +{ + io_req_t ior; + struct ifnet *ifp = &sc->sc_if; + + /* + * Drop packets if interface is down. + */ + if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) { + while (1) { + IF_DEQUEUE(&ifp->if_snd, ior); + if (ior == 0) + return; + iodone(ior); + } + } + /* + * If transmitter is busy, bail out. + */ + if (sc->sc_oactive) + return; + + /* + * Dequeue a packet. + */ + IF_DEQUEUE(&ifp->if_snd, ior); + if (ior == 0) + return; + + /* Mask interrupts from the ethercard. */ + outb( sc->sc_port + EN0_IMR, 0x00); + + if (sc->sc_pingpong) { + int count, output_page; + + if (sc->sc_tx1 == 0) { + output_page = sc->sc_txstrtpg; + sc->sc_tx1 = count = (*sc->sc_output)(sc, + ior->io_count, + ior->io_data, + sc->sc_txstrtpg); + } else if (sc->sc_tx2 == 0) { + output_page = sc->sc_txstrtpg + 6; + sc->sc_tx2 = count = (*sc->sc_output)(sc, + ior->io_count, + ior->io_data, + output_page); + } else { + sc->sc_oactive = 1; + IF_PREPEND(&ifp->if_snd, ior); + return; + } + + DEBUGF({ + struct ether_header *eh; + + eh = (struct ether_header *)ior->io_data; + printf("send: %s%d: %x:%x:%x:%x:%x:%x, " + "olen %d, len %d\n", + sc->sc_name, sc->sc_unit, + eh->ether_dhost[0], eh->ether_dhost[1], + eh->ether_dhost[2], eh->ether_dhost[3], + eh->ether_dhost[4], eh->ether_dhost[5], + ior->io_count, count); + }); + + if (!sc->sc_txing) { + nsxmit(sc, count, output_page); + if (output_page == sc->sc_txstrtpg) + sc->sc_tx1 = -1, sc->sc_lasttx = -1; + else + sc->sc_tx2 = -1, sc->sc_lasttx = -2; + } + sc->sc_oactive = (sc->sc_tx1 && sc->sc_tx2); + } else { + int count; + + count = (*sc->sc_output)(sc, ior->io_count, + ior->io_data, sc->sc_txstrtpg); + + DEBUGF({ + struct ether_header *eh; + + eh = (struct ether_header *)ior->io_data; + printf("send: %s%d: %x:%x:%x:%x:%x:%x, " + "olen %d, len %d\n", + sc->sc_name, sc->sc_unit, + eh->ether_dhost[0], eh->ether_dhost[1], + eh->ether_dhost[2], eh->ether_dhost[3], + eh->ether_dhost[4], eh->ether_dhost[5], + ior->io_count, count); + }); + + nsxmit(sc, count, sc->sc_txstrtpg); + sc->sc_oactive = 1; + } + + /* reenable 8390 interrupts. */ + outb_p(sc->sc_port + EN0_IMR, ENISR_ALL); + + iodone(ior); +} + +/* + * Interrupt routine. + * Called by board level driver. + */ +void +nsintr(sc) + struct nssoftc *sc; +{ + int port = sc->sc_port; + int interrupts, boguscount = 0; + struct ifnet *ifp = &sc->sc_if; + + if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) { + DEBUGF(printf("nsintr: %s%d: interface down\n", + sc->sc_name, sc->sc_unit)); + return; + } + + /* + * Change to page 0 and read intr status reg. + */ + outb_p(port + E8390_CMD, E8390_NODMA+E8390_PAGE0); + + while ((interrupts = inb_p(port + EN0_ISR)) != 0 && ++boguscount < 9) { + if (interrupts & ENISR_RDC) { + /* + * Ack meaningless DMA complete. + */ + outb_p(port + EN0_ISR, ENISR_RDC); + } + + if (interrupts & ENISR_OVER) + nsrxoverrun(sc); + else if (interrupts & (ENISR_RX+ENISR_RX_ERR)) { + nsrint(sc); + } + + if (interrupts & ENISR_TX) { + nsxint(sc); + } + else if (interrupts & ENISR_COUNTERS) { + /* + * XXX - We really should be storing statistics + * about the interface. For now we just drop them. + */ + + /* reading resets the counters! */ + (void) inb_p(port + EN0_COUNTER0); /* frame */ + (void) inb_p(port + EN0_COUNTER1); /* crc */ + (void) inb_p(port + EN0_COUNTER2); /* miss */ + + DEBUGF(printf("%s%d: acked counter interrupt.\n", + sc->sc_name, sc->sc_unit)); + + outb_p(port + EN0_ISR, ENISR_COUNTERS); /* ack intr */ + } + + if (interrupts & ENISR_TX_ERR) { + DEBUGF(printf("acking transmit error\n")); + outb_p(port + EN0_ISR, ENISR_TX_ERR); /* ack intr */ + } + + outb_p(port + E8390_CMD, E8390_NODMA+E8390_PAGE0+E8390_START); + } + + DEBUGF({ + if (interrupts) { + printf("%s%d: unknown interrupt 0x%x", + sc->sc_name, sc->sc_unit, interrupts); + outb_p(port + E8390_CMD, + E8390_NODMA+E8390_PAGE0+E8390_START); + outb_p(port + EN0_ISR, 0xff); /* ack all intrs */ + } + }) +} + +/* + * Process a transmit interrupt. + */ +void +nsxint(sc) + struct nssoftc *sc; +{ + int port = sc->sc_port, status; + struct ifnet *ifp = &sc->sc_if; + + status = inb(port + EN0_TSR); + outb_p(port + EN0_ISR, ENISR_TX); /* ack intr */ + + sc->sc_txing = 0; + sc->sc_timer = 0; + sc->sc_oactive = 0; + + if (sc->sc_pingpong) { + if (sc->sc_tx1 < 0) { + if (sc->sc_lasttx != 1 && sc->sc_lasttx != -1) + printf("%s%d: bogus last_tx_buffer %d," + "tx1 = %d\n", + sc->sc_name, sc->sc_unit, + sc->sc_lasttx, sc->sc_tx1); + sc->sc_tx1 = 0; + if (sc->sc_tx2 > 0) { + nsxmit(sc, sc->sc_tx2, sc->sc_txstrtpg + 6); + sc->sc_tx2 = -1; + sc->sc_lasttx = 2; + } else + sc->sc_lasttx = 20; + } else if (sc->sc_tx2 < 0) { + if (sc->sc_lasttx != 2 && sc->sc_lasttx != -2) + printf("%s%d: bogus last_tx_buffer %d," + "tx2 = %d\n", + sc->sc_name, sc->sc_unit, + sc->sc_lasttx, sc->sc_tx2); + sc->sc_tx2 = 0; + if (sc->sc_tx1 > 0) { + nsxmit(sc, sc->sc_tx1, sc->sc_txstrtpg); + sc->sc_tx1 = -1; + sc->sc_lasttx = 1; + } else + sc->sc_lasttx = 10; + } else + printf("%s%d: unexpected TX-done interrupt, " + "lasttx = %d\n", + sc->sc_name, sc->sc_unit, sc->sc_lasttx); + } + /* + * Update stats. + */ + if (status & ENTSR_COL) { + if (status & ENTSR_ABT) + ifp->if_collisions += 16; + else + ifp->if_collisions += inb(port + EN0_NCR); + } + if (status & ENTSR_PTX) { + DEBUGF(printf("sent: %s%d\n", sc->sc_name, sc->sc_unit)); + ifp->if_opackets++; + } else + ifp->if_oerrors++; + + /* + * Start output on interface. + */ + nsstart(sc); +} + +/* + * Process a receive interrupt. + */ +void +nsrint(sc) + struct nssoftc *sc; +{ + int port = sc->sc_port; + int rxing_page, this_frame, next_frame, current_offset; + int rx_pkt_count = 0; + int num_rx_pages = sc->sc_stoppg - sc->sc_rxstrtpg; + struct nspkthdr rx_frame; + struct ifnet *ifp = &sc->sc_if; + + while (++rx_pkt_count < 10) { + int pkt_len; + + /* + * Get the rx page (incoming packet pointer). + */ + outb_p(port + E8390_CMD, E8390_NODMA+E8390_PAGE1); + rxing_page = inb_p(port + EN1_CURPAG); + outb_p(port + E8390_CMD, E8390_NODMA+E8390_PAGE0); + + /* + * Remove one frame from the ring. + * Boundary is always a page behind. + */ + this_frame = inb_p(port + EN0_BOUNDARY) + 1; + if (this_frame >= sc->sc_stoppg) + this_frame = sc->sc_rxstrtpg; + + DEBUGF({ + if (this_frame != sc->sc_curpg) + printf("%s%d: mismatched read page pointers " + "%x vs %x\n", + sc->sc_name, sc->sc_unit, + this_frame, sc->sc_curpg); + }); + + if (this_frame == rxing_page) { + DEBUGF(printf("this_frame = rxing_page!\n")); + break; + } + + current_offset = this_frame << 8; + (*sc->sc_input)(sc, sizeof(rx_frame), (char *)&rx_frame, + current_offset); + + pkt_len = rx_frame.count - sizeof(rx_frame); + + next_frame = this_frame + 1 + ((pkt_len + 4) >> 8); + + if (rx_frame.next != next_frame + && rx_frame.next != next_frame + 1 + && rx_frame.next != next_frame - num_rx_pages + && rx_frame.next != next_frame + 1 - num_rx_pages) { + sc->sc_curpg = rxing_page; + outb(port + EN0_BOUNDARY, sc->sc_curpg - 1); + ifp->if_ierrors++; + DEBUGF(printf("INPUT ERROR?\n")); + continue; + } + if (pkt_len < 60 || pkt_len > 1518) { + ifp->if_ierrors++; + DEBUGF(printf("%s%d: bad packet length %d\n", + sc->sc_name, sc->sc_unit, pkt_len)); + } else if ((rx_frame.status & 0x0f) == ENRSR_RXOK) { + ipc_kmsg_t kmsg; + + kmsg = net_kmsg_get(); + if (kmsg == 0) { + DEBUGF(printf("%s%d: dropped packet\n", + sc->sc_name, sc->sc_unit)); + ifp->if_rcvdrops++; + } else { + int len, off; + struct ether_header *eh; + struct packet_header *pkt; + + ifp->if_ipackets++; + off = current_offset + sizeof(rx_frame); + eh = ((struct ether_header *) + (&net_kmsg(kmsg)->header[0])); + (*sc->sc_input)(sc, + sizeof(struct ether_header), + (char *)eh, off); + off += sizeof(struct ether_header); + len = pkt_len - sizeof(struct ether_header); + + DEBUGF(printf("rcv: %s%d: %x:%x:%x:%x:%x:%x, " + "len %d, type 0x%x\n", + sc->sc_name, sc->sc_unit, + eh->ether_shost[0], + eh->ether_shost[1], + eh->ether_shost[2], + eh->ether_shost[3], + eh->ether_shost[4], + eh->ether_shost[5], + len, eh->ether_type)); + + pkt = ((struct packet_header *) + (&net_kmsg(kmsg)->packet[0])); + (*sc->sc_input)(sc, len, (char *)(pkt+1), off); + pkt->type = eh->ether_type; + pkt->length = len+sizeof(struct packet_header); + net_packet(ifp, kmsg, pkt->length, + ethernet_priority(kmsg)); + } + } else { + DEBUGF(printf("%s%d: bogus packet: " + "status=0x%x nxpg=0x%x size=%d\n", + sc->sc_name, sc->sc_unit, + rx_frame.status, rx_frame.next, + rx_frame.count)); + ifp->if_ierrors++; + } + next_frame = rx_frame.next; + if (next_frame >= sc->sc_stoppg) { + DEBUGF(printf("%s%d: next frame inconsistency, 0x%x\n", + sc->sc_name, sc->sc_unit, next_frame)); + next_frame = sc->sc_rxstrtpg; + } + sc->sc_curpg = next_frame; + outb(port + EN0_BOUNDARY, next_frame - 1); + } + + /* + * Bug alert! Reset ENISR_OVER to avoid spurious overruns! + */ + outb_p(port + EN0_ISR, ENISR_RX+ENISR_RX_ERR+ENISR_OVER); +} + +/* + * Handle a receive overrun condition. + * + * XXX - this needs to be gone over in light of the NS documentation. + */ +void +nsrxoverrun(sc) + struct nssoftc *sc; +{ + int port = sc->sc_port, i; + extern unsigned delaycount; + + printf("%s%d: receive overrun\n", sc->sc_name, sc->sc_unit); + + /* + * We should already be stopped and in page0, but just to be sure... + */ + outb_p(port + E8390_CMD, E8390_NODMA+E8390_PAGE0+E8390_STOP); + + /* + * Clear remote byte counter registers. + */ + outb_p(port + EN0_RCNTLO, 0); + outb_p(port + EN0_RCNTHI, 0); + + /* + * Wait for reset to complete. + */ + for (i = delaycount*2; i && !(inb_p(port+EN0_ISR) & ENISR_RESET); i--) + ; + if (i == 0) { + printf("%s%d: reset did not complete at overrun\n", + sc->sc_name, sc->sc_unit); + nsinit(sc); + return; + } + /* + * Disable transmitter. + */ + outb_p(port + EN0_TXCR, E8390_TXOFF); + + /* + * Remove packets. + */ + nsrint(sc); + + outb_p(port + EN0_ISR, 0xff); + outb_p(port + E8390_CMD, E8390_NODMA+E8390_PAGE0+E8390_START); + outb_p(port + EN0_TXCR, E8390_TXCONFIG); +} + +/* + * Trigger a transmit start. + */ +void +nsxmit(sc, length, start_page) + struct nssoftc *sc; + unsigned length; + int start_page; +{ + int port = sc->sc_port; + + sc->sc_txing = 1; + outb_p(port, E8390_NODMA+E8390_PAGE0); + if (inb_p(port) & E8390_TRANS) { + printf("%s%d: nsxmit() called with the transmitter busy\n", + sc->sc_name, sc->sc_unit); + return; + } + outb_p(port + EN0_TCNTLO, length & 0xff); + outb_p(port + EN0_TCNTHI, (length >> 8) & 0xff); + outb_p(port + EN0_TPSR, start_page); + outb_p(port, E8390_NODMA+E8390_TRANS+E8390_START); + sc->sc_timer = 4; +} + +#endif /* NUL > 0 || NWD > 0 */ |