summaryrefslogtreecommitdiff
path: root/i386/i386at/gpl/if_wd.c
diff options
context:
space:
mode:
Diffstat (limited to 'i386/i386at/gpl/if_wd.c')
-rw-r--r--i386/i386at/gpl/if_wd.c581
1 files changed, 581 insertions, 0 deletions
diff --git a/i386/i386at/gpl/if_wd.c b/i386/i386at/gpl/if_wd.c
new file mode 100644
index 0000000..c569a3e
--- /dev/null
+++ b/i386/i386at/gpl/if_wd.c
@@ -0,0 +1,581 @@
+/*
+ * 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 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 <wd.h>
+#if NWD > 0
+/*
+ * Driver for SMC/Western Digital Ethernet adaptors.
+ * Derived from the Linux driver by Donald Becker.
+ *
+ * Shantanu Goel (goel@cs.columbia.edu)
+ */
+#include <mach/sa/sys/types.h>
+#include "vm_param.h"
+#include <kern/time_out.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 WD_START_PG 0x00 /* first page of TX buffer */
+#define WD03_STOP_PG 0x20 /* last page +1 of RX ring */
+#define WD13_STOP_PG 0x40 /* last page +1 of RX ring */
+
+#define WD_CMDREG 0 /* offset of ASIC command register */
+#define WD_RESET 0x80 /* board reset in WDTRA_CMDREG */
+#define WD_MEMEN 0x40 /* enable shared memory */
+#define WD_CMDREG5 5 /* offset of 16-bit-only ASIC register 5 */
+#define ISA16 0x80 /* enable 16 bit access from the ISA bus */
+#define NIC16 0x40 /* enable 16 bit access from the 8390 */
+#define WD_NIC_OFF 16 /* NIC register offset */
+
+#define wdunit(dev) minor(dev)
+
+/*
+ * Autoconfiguration stuff.
+ */
+int wdprobe();
+void wdattach();
+int wdstd[] = { 0x300, 0x280, 0x380, 0x240, 0 };
+struct bus_device *wdinfo[NWD];
+struct bus_driver wddriver = {
+ wdprobe, 0, wdattach, 0, wdstd, "wd", wdinfo, 0, 0, 0
+};
+
+/*
+ * NS8390 state.
+ */
+struct nssoftc wdnssoftc[NWD];
+
+/*
+ * Board state.
+ */
+struct wdsoftc {
+ int sc_mstart; /* start of board's RAM */
+ int sc_mend; /* end of board's RAM */
+ int sc_rmstart; /* start of receive RAM */
+ int sc_rmend; /* end of receive RAM */
+ int sc_reg0; /* copy of register 0 of ASIC */
+ int sc_reg5; /* copy of register 5 of ASIC */
+} wdsoftc[NWD];
+
+void wdstart(int);
+void wd_reset(struct nssoftc *sc);
+void wd_input(struct nssoftc *sc, int, char *, int);
+int wd_output(struct nssoftc *sc, int, char *, int);
+
+/*
+ * Watchdog.
+ */
+int wdwstart = 0;
+void wdwatch(void);
+
+#define WDDEBUG
+#ifdef WDDEBUG
+int wddebug = 0;
+#define DEBUGF(stmt) { if (wddebug) stmt; }
+#else
+#define DEBUGF(stmt)
+#endif
+
+/*
+ * Probe for the WD8003 and WD8013.
+ * These cards have the station address PROM at I/O ports <base>+8
+ * to <base>+13, with a checksum following. A Soundblaster can have
+ * the same checksum as a WD ethercard, so we have an extra exclusionary
+ * check for it.
+ */
+int
+wdprobe(xxx, ui)
+ int xxx;
+ struct bus_device *ui;
+{
+ int *port;
+
+ if (ui->unit >= NWD) {
+ printf("wd%d: not configured\n", ui->unit);
+ return (0);
+ }
+ for (port = wdstd; *port; port++) {
+ if (*port < 0)
+ continue;
+ if (inb(*port + 8) != 0xff
+ && inb(*port + 9) != 0xff
+ && wdprobe1(*port, ui)) {
+ ui->address = *port;
+ *port = -1;
+ return (1);
+ }
+ }
+ return (0);
+}
+
+int
+wdprobe1(port, ui)
+ int port;
+ struct bus_device *ui;
+{
+
+ int i, irq = 0, checksum = 0, ancient = 0, word16 = 0;
+ struct wdsoftc *wd = &wdsoftc[ui->unit];
+ struct nssoftc *ns = &wdnssoftc[ui->unit];
+ struct ifnet *ifp = &ns->sc_if;
+
+ for (i = 0; i < 8; i++)
+ checksum += inb(port + 8 + i);
+ if ((checksum & 0xff) != 0xff)
+ return (0);
+
+ printf("wd%d: WD80x3 at 0x%03x, ", ui->unit, port);
+ for (i = 0; i < ETHER_ADDR_LEN; i++) {
+ if (i == 0)
+ printf("%02x", ns->sc_addr[i] = inb(port + 8 + i));
+ else
+ printf(":%02x", ns->sc_addr[i] = inb(port + 8 + i));
+ }
+ /*
+ * Check for PureData.
+ */
+ if (inb(port) == 'P' && inb(port + 1) == 'D') {
+ u_char reg5 = inb(port + 5);
+
+ switch (inb(port + 2)) {
+
+ case 0x03:
+ case 0x05:
+ word16 = 0;
+ break;
+
+ case 0x0a:
+ word16 = 1;
+ break;
+
+ default:
+ word16 = 0;
+ break;
+ }
+ wd->sc_mstart = ((reg5 & 0x1c) + 0xc0) << 12;
+ irq = (reg5 & 0xe0) == 0xe0 ? 10 : (reg5 >> 5) + 1;
+ } else {
+ /*
+ * Check for 8 bit vs 16 bit card.
+ */
+ for (i = 0; i < ETHER_ADDR_LEN; i++)
+ if (inb(port + i) != inb(port + 8 + i))
+ break;
+ if (i >= ETHER_ADDR_LEN) {
+ ancient = 1;
+ word16 = 0;
+ } else {
+ int tmp = inb(port + 1);
+
+ /*
+ * Attempt to clear 16bit bit.
+ */
+ outb(port + 1, tmp ^ 0x01);
+ if (((inb(port + 1) & 0x01) == 0x01) /* 16 bit */
+ && (tmp & 0x01) == 0x01) { /* in 16 bit slot */
+ int asic_reg5 = inb(port + WD_CMDREG5);
+
+ /*
+ * Magic to set ASIC to word-wide mode.
+ */
+ outb(port+WD_CMDREG5, NIC16|(asic_reg5&0x1f));
+ outb(port + 1, tmp);
+ word16 = 1;
+ } else
+ word16 = 0;
+ outb(port + 1, tmp);
+ }
+ if (!ancient && (inb(port + 1) & 0x01) != (word16 & 0x01))
+ printf("\nwd%d: bus width conflict, "
+ "%d (probe) != %d (reg report)", ui->unit,
+ word16 ? 16 : 8, (inb(port+1) & 0x01) ? 16 : 8);
+ }
+ /*
+ * Determine board's RAM location.
+ */
+ if (wd->sc_mstart == 0) {
+ int reg0 = inb(port);
+
+ if (reg0 == 0xff || reg0 == 0)
+ wd->sc_mstart = 0xd0000;
+ else {
+ int high_addr_bits = inb(port + WD_CMDREG5) & 0x1f;
+
+ if (high_addr_bits == 0x1f || word16 == 0)
+ high_addr_bits = 0x01;
+ wd->sc_mstart = ((reg0&0x3f)<<13)+(high_addr_bits<<19);
+ }
+ }
+ /*
+ * Determine irq.
+ */
+ if (irq == 0) {
+ int irqmap[] = { 9, 3, 5, 7, 10, 11, 15, 4 };
+ int reg1 = inb(port + 1);
+ int reg4 = inb(port + 4);
+
+ /*
+ * For old card, irq must be supplied.
+ */
+ if (ancient || reg1 == 0xff) {
+ if (ui->sysdep1 == 0) {
+ printf("\nwd%d: must specify IRQ for card\n",
+ ui->unit);
+ return (0);
+ }
+ irq = ui->sysdep1;
+ } else {
+ DEBUGF({
+ int i = ((reg4 >> 5) & 0x03) + (reg1 & 0x04);
+
+ printf("\nwd%d: irq index %d\n", ui->unit, i);
+ printf("wd%d:", ui->unit);
+ })
+ irq = irqmap[((reg4 >> 5) & 0x03) + (reg1 & 0x04)];
+ }
+ } else if (irq == 2)
+ irq = 9;
+ ui->sysdep1 = irq;
+ take_dev_irq(ui);
+ printf(", irq %d", irq);
+
+ /*
+ * Initialize 8390 state.
+ */
+ ns->sc_name = ui->name;
+ ns->sc_unit = ui->unit;
+ ns->sc_port = port + WD_NIC_OFF;
+ ns->sc_reset = wd_reset;
+ ns->sc_input = wd_input;
+ ns->sc_output = wd_output;
+ ns->sc_pingpong = 1;
+ ns->sc_word16 = word16;
+ ns->sc_txstrtpg = WD_START_PG;
+ ns->sc_rxstrtpg = WD_START_PG + TX_PAGES(ns);
+ ns->sc_stoppg = word16 ? WD13_STOP_PG : WD03_STOP_PG;
+
+ wd->sc_rmstart = wd->sc_mstart + TX_PAGES(ns) * 256;
+ wd->sc_mend = wd->sc_rmend
+ = wd->sc_mstart + (ns->sc_stoppg - WD_START_PG) * 256;
+ printf(", memory 0x%05x-0x%05x", wd->sc_mstart, wd->sc_mend);
+
+ if (word16)
+ printf(", 16 bit");
+ printf("\n");
+
+ DEBUGF(printf("wd%d: txstrtpg %d rxstrtpg %d num_pages %d\n",
+ ui->unit, ns->sc_txstrtpg, ns->sc_rxstrtpg,
+ (wd->sc_mend - wd->sc_mstart) / 256));
+
+ /*
+ * Initialize interface header.
+ */
+ ifp->if_unit = ui->unit;
+ ifp->if_mtu = ETHERMTU;
+ ifp->if_flags = IFF_BROADCAST;
+ ifp->if_header_size = sizeof(struct ether_header);
+ ifp->if_header_format = HDR_ETHERNET;
+ ifp->if_address_size = ETHER_ADDR_LEN;
+ ifp->if_address = ns->sc_addr;
+ if_init_queues(ifp);
+
+ return (1);
+}
+
+void
+wdattach(ui)
+ struct bus_device *ui;
+{
+ /*
+ * void
+ */
+}
+
+int
+wdopen(dev, flag)
+ dev_t dev;
+ int flag;
+{
+ int unit = wdunit(dev), s;
+ struct bus_device *ui;
+ struct wdsoftc *wd;
+ struct nssoftc *ns;
+
+ if (unit >= NWD || (ui = wdinfo[unit]) == 0 || ui->alive == 0)
+ return (ENXIO);
+
+ /*
+ * Start watchdog.
+ */
+ if (!wdwstart) {
+ wdwstart++;
+ timeout(wdwatch, 0, hz);
+ }
+ wd = &wdsoftc[unit];
+ ns = &wdnssoftc[unit];
+ ns->sc_if.if_flags |= IFF_UP;
+ s = splimp();
+ wd->sc_reg0 = ((wd->sc_mstart >> 13) & 0x3f) | WD_MEMEN;
+ wd->sc_reg5 = ((wd->sc_mstart >> 19) & 0x1f) | NIC16;
+ if (ns->sc_word16)
+ outb(ui->address + WD_CMDREG5, wd->sc_reg5);
+ outb(ui->address, wd->sc_reg0);
+ nsinit(ns);
+ splx(s);
+ return (0);
+}
+
+int
+wdoutput(dev, ior)
+ dev_t dev;
+ io_req_t ior;
+{
+ int unit = wdunit(dev);
+ struct bus_device *ui;
+
+ if (unit >= NWD || (ui = wdinfo[unit]) == 0 || ui->alive == 0)
+ return (ENXIO);
+
+ return (net_write(&wdnssoftc[unit].sc_if, wdstart, ior));
+}
+
+int
+wdsetinput(dev, receive_port, priority, filter, filter_count)
+ dev_t dev;
+ mach_port_t receive_port;
+ int priority;
+ filter_t *filter;
+ unsigned filter_count;
+{
+ int unit = wdunit(dev);
+ struct bus_device *ui;
+
+ if (unit >= NWD || (ui = wdinfo[unit]) == 0 || ui->alive == 0)
+ return (ENXIO);
+
+ return (net_set_filter(&wdnssoftc[unit].sc_if, receive_port,
+ priority, filter, filter_count));
+}
+
+int
+wdgetstat(dev, flavor, status, count)
+ dev_t dev;
+ int flavor;
+ dev_status_t status;
+ unsigned *count;
+{
+ int unit = wdunit(dev);
+ struct bus_device *ui;
+
+ if (unit >= NWD || (ui = wdinfo[unit]) == 0 || ui->alive == 0)
+ return (ENXIO);
+
+ return (net_getstat(&wdnssoftc[unit].sc_if, flavor, status, count));
+}
+
+int
+wdsetstat(dev, flavor, status, count)
+ dev_t dev;
+ int flavor;
+ dev_status_t status;
+ unsigned count;
+{
+ int unit = wdunit(dev), oflags, s;
+ struct bus_device *ui;
+ struct ifnet *ifp;
+ struct net_status *ns;
+
+ if (unit >= NWD || (ui = wdinfo[unit]) == 0 || ui->alive == 0)
+ return (ENXIO);
+
+ ifp = &wdnssoftc[unit].sc_if;
+
+ switch (flavor) {
+
+ case NET_STATUS:
+ if (count < NET_STATUS_COUNT)
+ return (D_INVALID_SIZE);
+ ns = (struct net_status *)status;
+ oflags = ifp->if_flags & (IFF_ALLMULTI|IFF_PROMISC);
+ ifp->if_flags &= ~(IFF_ALLMULTI|IFF_PROMISC);
+ ifp->if_flags |= ns->flags & (IFF_ALLMULTI|IFF_PROMISC);
+ if ((ifp->if_flags & (IFF_ALLMULTI|IFF_PROMISC)) != oflags) {
+ s = splimp();
+ nsinit(&wdnssoftc[unit]);
+ splx(s);
+ }
+ break;
+
+ default:
+ return (D_INVALID_OPERATION);
+ }
+ return (D_SUCCESS);
+}
+
+void
+wdintr(unit)
+ int unit;
+{
+ nsintr(&wdnssoftc[unit]);
+}
+
+void
+wdstart(unit)
+ int unit;
+{
+ nsstart(&wdnssoftc[unit]);
+}
+
+void
+wd_reset(ns)
+ struct nssoftc *ns;
+{
+ int port = ns->sc_port - WD_NIC_OFF; /* ASIC base address */
+ struct wdsoftc *wd = &wdsoftc[ns->sc_unit];
+
+ outb(port, WD_RESET);
+ outb(0x80, 0); /* I/O delay */
+ /*
+ * Set up the ASIC registers, just in case something changed them.
+ */
+ outb(port, ((wd->sc_mstart >> 13) & 0x3f) | WD_MEMEN);
+ if (ns->sc_word16)
+ outb(port + WD_CMDREG5, NIC16 | ((wd->sc_mstart>>19) & 0x1f));
+}
+
+void
+wd_input(ns, count, buf, ring_offset)
+ struct nssoftc *ns;
+ int count;
+ char *buf;
+ int ring_offset;
+{
+ int port = ns->sc_port - WD_NIC_OFF;
+ int xfer_start;
+ struct wdsoftc *wd = &wdsoftc[ns->sc_unit];
+
+ DEBUGF(printf("wd%d: ring_offset = %d\n", ns->sc_unit, ring_offset));
+
+ xfer_start = wd->sc_mstart + ring_offset - (WD_START_PG << 8);
+
+ /*
+ * The NIC driver calls us 3 times. Once to read the NIC 4 byte
+ * header, next to read the Ethernet header and finally to read
+ * the actual data. We enable 16 bit mode before the NIC header
+ * and disable it after the packet body.
+ */
+ if (count == 4) {
+ if (ns->sc_word16)
+ outb(port + WD_CMDREG5, ISA16 | wd->sc_reg5);
+ ((int *)buf)[0] = ((int *)phystokv(xfer_start))[0];
+ return;
+ }
+ if (count == sizeof(struct ether_header)) {
+ xfer_start = (int)phystokv(xfer_start);
+ ((int *)buf)[0] = ((int *)xfer_start)[0];
+ ((int *)buf)[1] = ((int *)xfer_start)[1];
+ ((int *)buf)[2] = ((int *)xfer_start)[2];
+ ((short *)(buf + 12))[0] = ((short *)(xfer_start + 12))[0];
+ return;
+ }
+ if (xfer_start + count > wd->sc_rmend) {
+ int semi_count = wd->sc_rmend - xfer_start;
+
+ /*
+ * Input move must be wrapped.
+ */
+ bcopy((char *)phystokv(xfer_start), buf, semi_count);
+ count -= semi_count;
+ bcopy((char *)phystokv(wd->sc_rmstart),buf+semi_count,count);
+ } else
+ bcopy((char *)phystokv(xfer_start), buf, count);
+ if (ns->sc_word16)
+ outb(port + WD_CMDREG5, wd->sc_reg5);
+}
+
+int
+wd_output(ns, count, buf, start_page)
+ struct nssoftc *ns;
+ int count;
+ char *buf;
+ int start_page;
+{
+ char *shmem;
+ int i, port = ns->sc_port - WD_NIC_OFF;
+ struct wdsoftc *wd = &wdsoftc[ns->sc_unit];
+
+ DEBUGF(printf("wd%d: start_page = %d\n", ns->sc_unit, start_page));
+
+ shmem = (char *)phystokv(wd->sc_mstart+((start_page-WD_START_PG)<<8));
+ if (ns->sc_word16) {
+ outb(port + WD_CMDREG5, ISA16 | wd->sc_reg5);
+ bcopy(buf, shmem, count);
+ outb(port + WD_CMDREG5, wd->sc_reg5);
+ } else
+ bcopy(buf, shmem, count);
+ while (count < ETHERMIN + sizeof(struct ether_header)) {
+ *(shmem + count) = 0;
+ count++;
+ }
+ return (count);
+}
+
+/*
+ * Watchdog.
+ * Check for hung transmissions.
+ */
+void
+wdwatch()
+{
+ int unit, s;
+ struct nssoftc *ns;
+
+ timeout(wdwatch, 0, hz);
+
+ s = splimp();
+ for (unit = 0; unit < NWD; unit++) {
+ if (wdinfo[unit] == 0 || wdinfo[unit]->alive == 0)
+ continue;
+ ns = &wdnssoftc[unit];
+ if (ns->sc_timer && --ns->sc_timer == 0) {
+ printf("wd%d: transmission timeout\n", unit);
+ nsinit(ns);
+ }
+ }
+ splx(s);
+}
+
+#endif /* NWD > 0 */