summaryrefslogtreecommitdiff
path: root/linux/src/drivers/net/eexpress.c
diff options
context:
space:
mode:
authorThomas Bushnell <thomas@gnu.org>1999-04-26 05:58:44 +0000
committerThomas Bushnell <thomas@gnu.org>1999-04-26 05:58:44 +0000
commit86297c41a26f18d924e64fc93321c59cbc4c48dd (patch)
tree376954c6b95b735d361875319a1a2a9db6a27527 /linux/src/drivers/net/eexpress.c
parent851137902d3e7ad87af177487df3eea53e940a1c (diff)
1998-11-30 OKUJI Yoshinori <okuji@kuicr.kyoto-u.ac.jp>
Clean up linux emulation code to make it architecture-independent as much as possible. * linux: Renamed from linuxdev. * Makefile.in (objfiles): Add linux.o instead of linuxdev.o. (MAKE): New variable. Used for the linux.o target. * configure.in: Add AC_CHECK_TOOL(MAKE, make). * i386/i386/spl.h: Include <i386/ipl.h>, for compatibility with OSF Mach 3.0. Suggested by Elgin Lee <ehl@funghi.com>. * linux/src: Renamed from linux/linux. * linux/dev: Renamed from linux/mach. * linux/Drivers.in (AC_INIT): Use dev/include/linux/autoconf.h, instead of mach/include/linux/autoconf.h. * Makefile.in (all): Target ../linux.o instead of ../linuxdev.o. * linux/dev/drivers/block/genhd.c: Include <machine/spl.h> instead of <i386/ipl.h>. * linux/dev/drivers/net/auto_irq.c: Remove unneeded header files, <i386/ipl.h> and <i386/pic.h>. * linux/dev/init/main.c: Many i386-dependent codes moved to ... * linux/dev/arch/i386/irq.c: ... here. * linux/dev/arch/i386/setup.c: New file. * linux/dev/arch/i386/linux_emul.h: Likewise. * linux/dev/arch/i386/glue/timer.c: Merged into sched.c. * linux/dev/arch/i386/glue/sched.c: Include <machine/spl.h> instead of <i386/ipl.h>, and moved to ... * linux/dev/kernel/sched.c: ... here. * linux/dev/arch/i386/glue/block.c: Include <machine/spl.h> and <linux_emul.h>, instead of i386-dependent header files, and moved to ... * linux/dev/glue/blocl.c: ... here. * linux/dev/arch/i386/glue/net.c: Include <machine/spl.h> and <linux_emul.h>, instead of i386-dependent header files, and moved to ... * linux/dev/glue/net.c: ... here. * linux/dev/arch/i386/glue/misc.c: Remove `x86' and moved to ... * linux/dev/glue/misc.c: ... here. * linux/dev/arch/i386/glue/kmem.c: Moved to ... * linux/dev/glue/kmem.c: ... here.
Diffstat (limited to 'linux/src/drivers/net/eexpress.c')
-rw-r--r--linux/src/drivers/net/eexpress.c1285
1 files changed, 1285 insertions, 0 deletions
diff --git a/linux/src/drivers/net/eexpress.c b/linux/src/drivers/net/eexpress.c
new file mode 100644
index 0000000..d706550
--- /dev/null
+++ b/linux/src/drivers/net/eexpress.c
@@ -0,0 +1,1285 @@
+/* $Id: eexpress.c,v 1.1 1999/04/26 05:52:09 tb Exp $
+ *
+ * Intel EtherExpress device driver for Linux
+ *
+ * Original version written 1993 by Donald Becker
+ * Modularized by Pauline Middelink <middelin@polyware.iaf.nl>
+ * Changed to support io= irq= by Alan Cox <Alan.Cox@linux.org>
+ * Reworked 1995 by John Sullivan <js10039@cam.ac.uk>
+ * More fixes by Philip Blundell <pjb27@cam.ac.uk>
+ * Added the Compaq LTE Alan Cox <alan@redhat.com>
+ *
+ * Note - this driver is experimental still - it has problems on faster
+ * machines. Someone needs to sit down and go through it line by line with
+ * a databook...
+ */
+
+/*
+ * The original EtherExpress driver was just about usable, but
+ * suffered from a long startup delay, a hard limit of 16k memory
+ * usage on the card (EtherExpress 16s have either 32k or 64k),
+ * and random locks under load. The last was particularly annoying
+ * and made running eXceed/W preferable to Linux/XFree. After hacking
+ * through the driver for a couple of days, I had fixed most of the
+ * card handling errors, at the expense of turning the code into
+ * a complete jungle, but still hadn't tracked down the lock-ups.
+ * I had hoped these would be an IP bug, but failed to reproduce them
+ * under other drivers, so decided to start from scratch and rewrite
+ * the driver cleanly. And here it is.
+ *
+ * It's still not quite there, but self-corrects a lot more problems.
+ * the 'CU wedged, resetting...' message shouldn't happen at all, but
+ * at least we recover. It still locks occasionally, any ideas welcome.
+ *
+ * The original startup delay experienced by some people was due to the
+ * first ARP request for the address of the default router getting lost.
+ * (mostly the reply we were getting back was arriving before our
+ * hardware address was set up, or before the configuration sequence
+ * had told the card NOT to strip of the frame header). If you a long
+ * startup delay, you may have lost this ARP request/reply, although
+ * the original cause has been fixed. However, it is more likely that
+ * you've just locked under this version.
+ *
+ * The main changes are in the 586 initialization procedure (which was
+ * just broken before - the EExp is a strange beasty and needs careful
+ * handling) the receive buffer handling (we now use a non-terminating
+ * circular list of buffers, which stops the card giving us out-of-
+ * resources errors), and the transmit code. The driver is also more
+ * structured, and I have tried to keep the kernel interface separate
+ * from the hardware interface (although some routines naturally want
+ * to do both).
+ *
+ * John Sullivan
+ *
+ * 18/5/95:
+ *
+ * The lock-ups seem to happen when you access card memory after a 586
+ * reset. This happens only 1 in 12 resets, on a random basis, and
+ * completely locks the machine. As far as I can see there is no
+ * workaround possible - the only thing to be done is make sure we
+ * never reset the card *after* booting the kernel - once at probe time
+ * must be sufficient, and we'll just have to put up with that failing
+ * occasionally (or buy a new NIC). By the way, this looks like a
+ * definite card bug, since Intel's own driver for DOS does exactly the
+ * same.
+ *
+ * This bug makes switching in and out of promiscuous mode a risky
+ * business, since we must do a 586 reset each time.
+ */
+
+/*
+ * Sources:
+ *
+ * The original eexpress.c by Donald Becker
+ * Sources: the Crynwr EtherExpress driver source.
+ * the Intel Microcommunications Databook Vol.1 1990
+ *
+ * wavelan.c and i82586.h
+ * This was invaluable for the complete '586 configuration details
+ * and command format.
+ *
+ * The Crynwr sources (again)
+ * Not as useful as the Wavelan driver, but then I had eexpress.c to
+ * go off.
+ *
+ * The Intel EtherExpress 16 ethernet card
+ * Provided the only reason I want to see a working etherexpress driver.
+ * A lot of fixes came from just observing how the card (mis)behaves when
+ * you prod it.
+ *
+ */
+
+static char version[] =
+"eexpress.c: v0.10 04-May-95 John Sullivan <js10039@cam.ac.uk>\n"
+" v0.14 19-May-96 Philip Blundell <phil@tazenda.demon.co.uk>\n"
+" v0.15 04-Aug-98 Alan Cox <alan@redhat.com>\n";
+
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/string.h>
+#include <linux/in.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/malloc.h>
+
+/*
+ * Not actually used yet - may be implemented when the driver has
+ * been debugged!
+ *
+ * Debug Level Driver Status
+ * 0 Final release
+ * 1 Beta test
+ * 2
+ * 3
+ * 4 Report timeouts & 586 errors (normal debug level)
+ * 5 Report all major events
+ * 6 Dump sent/received packet contents
+ * 7 Report function entry/exit
+ */
+
+#ifndef NET_DEBUG
+#define NET_DEBUG 4
+#endif
+static unsigned int net_debug = NET_DEBUG;
+
+#undef F_DEB
+
+#include "eth82586.h"
+
+#define PRIV(x) ((struct net_local *)(x)->priv)
+#define EEXP_IO_EXTENT 16
+
+/*
+ * Private data declarations
+ */
+
+struct net_local
+{
+ struct enet_statistics stats;
+ unsigned long init_time; /* jiffies when eexp_hw_init586 called */
+ unsigned short rx_first; /* first rx buf, same as RX_BUF_START */
+ unsigned short rx_last; /* last rx buf */
+ unsigned short tx_head; /* next free tx buf */
+ unsigned short tx_reap; /* first in-use tx buf */
+ unsigned short tx_tail; /* previous tx buf to tx_head */
+ unsigned short tx_link; /* last known-executing tx buf */
+ unsigned short last_tx_restart; /* set to tx_link when we restart the CU */
+ unsigned char started;
+ unsigned char promisc;
+ unsigned short rx_buf_start;
+ unsigned short rx_buf_end;
+ unsigned short num_tx_bufs;
+ unsigned short num_rx_bufs;
+};
+
+unsigned short start_code[] = {
+ 0x0000, /* SCP: set bus to 16 bits */
+ 0x0000,0x0000, /* junk */
+ 0x0000,0x0000, /* address of ISCP (lo,hi) */
+
+ 0x0001, /* ISCP: busy - cleared after reset */
+ 0x0008,0x0000,0x0000, /* offset,address (lo,hi) of SCB */
+
+ 0x0000,0x0000, /* SCB: status, commands */
+ 0x0000,0x0000, /* links to first command block, first receive descriptor */
+ 0x0000,0x0000, /* CRC error, alignment error counts */
+ 0x0000,0x0000, /* out of resources, overrun error counts */
+
+ 0x0000,0x0000, /* pad */
+ 0x0000,0x0000,
+
+ 0x0000,Cmd_Config, /* startup configure sequence, at 0x0020 */
+ 0x0032, /* link to next command */
+ 0x080c, /* 12 bytes follow : fifo threshold=8 */
+ 0x2e40, /* don't rx bad frames : SRDY/ARDY => ext. sync. : preamble len=8
+ * take addresses from data buffers : 6 bytes/address */
+ 0x6000, /* default backoff method & priority : interframe spacing = 0x60 */
+ 0xf200, /* slot time=0x200 : max collision retry = 0xf */
+ 0x0000, /* no HDLC : normal CRC : enable broadcast : disable promiscuous/multicast modes */
+ 0x003c, /* minimum frame length = 60 octets) */
+
+ 0x0000,Cmd_INT|Cmd_SetAddr,
+ 0x003e, /* link to next command */
+ 0x0000,0x0000,0x0000, /* hardware address placed here, 0x0038 */
+ 0x0000,Cmd_END|Cmd_Nop, /* end of configure sequence */
+ 0x003e,
+
+ 0x0000
+
+};
+
+#define CONF_LINK 0x0020
+#define CONF_HW_ADDR 0x0038
+
+/* maps irq number to EtherExpress magic value */
+static char irqrmap[] = { 0,0,1,2,3,4,0,0,0,1,5,6,0,0,0,0 };
+
+/*
+ * Prototypes for Linux interface
+ */
+
+extern int express_probe(struct device *dev);
+static int eexp_open (struct device *dev);
+static int eexp_close(struct device *dev);
+static struct enet_statistics *eexp_stats(struct device *dev);
+static int eexp_xmit (struct sk_buff *buf, struct device *dev);
+
+static void eexp_irq (int irq, void *dev_addr, struct pt_regs *regs);
+static void eexp_set_multicast(struct device *dev);
+
+/*
+ * Prototypes for hardware access functions
+ */
+
+static void eexp_hw_rx (struct device *dev);
+static void eexp_hw_tx (struct device *dev, unsigned short *buf, unsigned short len);
+static int eexp_hw_probe (struct device *dev,unsigned short ioaddr);
+static unsigned short eexp_hw_readeeprom(unsigned short ioaddr, unsigned char location);
+
+static unsigned short eexp_hw_lasttxstat(struct device *dev);
+static void eexp_hw_txrestart (struct device *dev);
+
+static void eexp_hw_txinit (struct device *dev);
+static void eexp_hw_rxinit (struct device *dev);
+
+static void eexp_hw_init586 (struct device *dev);
+static void eexp_hw_ASICrst (struct device *dev);
+
+/*
+ * Linux interface
+ */
+
+/*
+ * checks for presence of EtherExpress card
+ */
+
+int express_probe(struct device *dev)
+{
+ unsigned short *port,ports[] = { 0x0300,0x0270,0x0320,0x0340,0 };
+ unsigned short ioaddr = dev->base_addr;
+
+ if (ioaddr&0xfe00)
+ return eexp_hw_probe(dev,ioaddr);
+ else if (ioaddr)
+ return ENXIO;
+
+ for ( port=&ports[0] ; *port ; port++ )
+ {
+ unsigned short sum = 0;
+ int i;
+ for ( i=0 ; i<4 ; i++ )
+ {
+ unsigned short t;
+ t = inb(*port + ID_PORT);
+ sum |= (t>>4) << ((t & 0x03)<<2);
+ }
+ if (sum==0xbaba && !eexp_hw_probe(dev,*port))
+ return 0;
+ }
+ return ENODEV;
+}
+
+/*
+ * open and initialize the adapter, ready for use
+ */
+
+static int eexp_open(struct device *dev)
+{
+ int irq = dev->irq;
+ unsigned short ioaddr = dev->base_addr;
+
+#if NET_DEBUG > 6
+ printk(KERN_DEBUG "%s: eexp_open()\n", dev->name);
+#endif
+
+ if (!irq || !irqrmap[irq])
+ return -ENXIO;
+
+ if (irq2dev_map[irq] ||
+ /* more consistent, surely? */
+ ((irq2dev_map[irq]=dev),0) ||
+ request_irq(irq,&eexp_irq,0,"eexpress",NULL))
+ return -EAGAIN;
+
+ request_region(ioaddr, EEXP_IO_EXTENT, "eexpress");
+ dev->tbusy = 0;
+ dev->interrupt = 0;
+ eexp_hw_init586(dev);
+ dev->start = 1;
+ MOD_INC_USE_COUNT;
+#if NET_DEBUG > 6
+ printk(KERN_DEBUG "%s: leaving eexp_open()\n", dev->name);
+#endif
+ return 0;
+}
+
+/*
+ * close and disable the interface, leaving
+ * the 586 in reset
+ */
+static int eexp_close(struct device *dev)
+{
+ unsigned short ioaddr = dev->base_addr;
+ int irq = dev->irq;
+
+ dev->tbusy = 1;
+ dev->start = 0;
+
+ outb(SIRQ_dis|irqrmap[irq],ioaddr+SET_IRQ);
+ PRIV(dev)->started = 0;
+ outw(SCB_CUsuspend|SCB_RUsuspend,ioaddr+SCB_CMD);
+ outb(0,ioaddr+SIGNAL_CA);
+ free_irq(irq,NULL);
+ irq2dev_map[irq] = NULL;
+ outb(i586_RST,ioaddr+EEPROM_Ctrl);
+ release_region(ioaddr,16);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/*
+ * Return interface stats
+ */
+
+static struct enet_statistics *eexp_stats(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+
+ /*
+ * Hmmm, this looks a little too easy... The card maintains
+ * some stats in the SCB, and I'm not convinced we're
+ * incrementing the most sensible statistics when the card
+ * returns an error (esp. slow DMA, out-of-resources)
+ */
+ return &lp->stats;
+}
+
+/*
+ * Called to transmit a packet, or to allow us to right ourselves
+ * if the kernel thinks we've died.
+ */
+
+static int eexp_xmit(struct sk_buff *buf, struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ unsigned short ioaddr = dev->base_addr;
+
+#if NET_DEBUG > 6
+ printk(KERN_DEBUG "%s: eexp_xmit()\n", dev->name);
+#endif
+
+ outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+ if (dev->tbusy)
+ {
+ /* This will happen, but hopefully not as often as when
+ * tbusy==0. If it happens too much, we probably ought
+ * to think about unwedging ourselves...
+ */
+ if (test_bit(0,(void *)&PRIV(dev)->started))
+ {
+ if ((jiffies - dev->trans_start)>5)
+ {
+ if (lp->tx_link==lp->last_tx_restart)
+ {
+ unsigned short boguscount=200,rsst;
+ printk(KERN_WARNING "%s: Retransmit timed out, status %04x, resetting...\n",
+ dev->name,inw(ioaddr+SCB_STATUS));
+ eexp_hw_txinit(dev);
+ lp->last_tx_restart = 0;
+ outw(lp->tx_link,ioaddr+SCB_CBL);
+ outw(0,ioaddr+SCB_STATUS);
+ outw(SCB_CUstart,ioaddr+SCB_CMD);
+ outb(0,ioaddr+SIGNAL_CA);
+ while (!SCB_complete(rsst=inw(ioaddr+SCB_STATUS)))
+ {
+ if (!--boguscount)
+ {
+ boguscount=200;
+ printk(KERN_WARNING "%s: Reset timed out status %04x, retrying...\n",
+ dev->name,rsst);
+ outw(lp->tx_link,ioaddr+SCB_CBL);
+ outw(0,ioaddr+SCB_STATUS);
+ outw(SCB_CUstart,ioaddr+SCB_CMD);
+ outb(0,ioaddr+SIGNAL_CA);
+ }
+ }
+ dev->tbusy = 0;
+ mark_bh(NET_BH);
+ }
+ else
+ {
+ unsigned short status = inw(ioaddr+SCB_STATUS);
+ if (SCB_CUdead(status))
+ {
+ unsigned short txstatus = eexp_hw_lasttxstat(dev);
+ printk(KERN_WARNING "%s: Transmit timed out, CU not active status %04x %04x, restarting...\n",
+ dev->name, status, txstatus);
+ eexp_hw_txrestart(dev);
+ }
+ else
+ {
+ unsigned short txstatus = eexp_hw_lasttxstat(dev);
+ if (dev->tbusy && !txstatus)
+ {
+ printk(KERN_WARNING "%s: CU wedged, status %04x %04x, resetting...\n",
+ dev->name,status,txstatus);
+ eexp_hw_init586(dev);
+ dev->tbusy = 0;
+ mark_bh(NET_BH);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ if ((jiffies-lp->init_time)>10)
+ {
+ unsigned short status = inw(ioaddr+SCB_STATUS);
+ printk(KERN_WARNING "%s: i82586 startup timed out, status %04x, resetting...\n",
+ dev->name, status);
+ eexp_hw_init586(dev);
+ dev->tbusy = 0;
+ mark_bh(NET_BH);
+ }
+ }
+ }
+
+ if (buf==NULL)
+ {
+ unsigned short status = inw(ioaddr+SCB_STATUS);
+ unsigned short txstatus = eexp_hw_lasttxstat(dev);
+ if (SCB_CUdead(status))
+ {
+ printk(KERN_WARNING "%s: CU has died! status %04x %04x, attempting to restart...\n",
+ dev->name, status, txstatus);
+ lp->stats.tx_errors++;
+ eexp_hw_txrestart(dev);
+ }
+ dev_tint(dev);
+ outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
+ dev_kfree_skb(buf, FREE_WRITE);
+ return 0;
+ }
+
+ if (set_bit(0,(void *)&dev->tbusy))
+ {
+ lp->stats.tx_dropped++;
+ }
+ else
+ {
+ unsigned short length = (ETH_ZLEN < buf->len) ? buf->len : ETH_ZLEN;
+ unsigned short *data = (unsigned short *)buf->data;
+
+ outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+ eexp_hw_tx(dev,data,length);
+ outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
+ }
+ dev_kfree_skb(buf, FREE_WRITE);
+ outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
+ return 0;
+}
+
+/*
+ * Handle an EtherExpress interrupt
+ * If we've finished initializing, start the RU and CU up.
+ * If we've already started, reap tx buffers, handle any received packets,
+ * check to make sure we've not become wedged.
+ */
+
+static void eexp_irq(int irq, void *dev_info, struct pt_regs *regs)
+{
+ struct device *dev = irq2dev_map[irq];
+ struct net_local *lp;
+ unsigned short ioaddr,status,ack_cmd;
+ unsigned short old_rp,old_wp;
+
+ if (dev==NULL)
+ {
+ printk(KERN_WARNING "net_interrupt(): irq %d for unknown device caught by EExpress\n",irq);
+ return;
+ }
+
+#if NET_DEBUG > 6
+ printk(KERN_DEBUG "%s: interrupt\n", dev->name);
+#endif
+
+ dev->interrupt = 1; /* should this be reset on exit? */
+
+ lp = (struct net_local *)dev->priv;
+ ioaddr = dev->base_addr;
+
+ outb(SIRQ_dis|irqrmap[irq],ioaddr+SET_IRQ);
+ old_rp = inw(ioaddr+READ_PTR);
+ old_wp = inw(ioaddr+WRITE_PTR);
+ status = inw(ioaddr+SCB_STATUS);
+ ack_cmd = SCB_ack(status);
+
+ if (PRIV(dev)->started==0 && SCB_complete(status))
+ {
+#if NET_DEBUG > 4
+ printk(KERN_DEBUG "%s: SCBcomplete event received\n", dev->name);
+#endif
+ while (SCB_CUstat(status)==2)
+ status = inw_p(ioaddr+SCB_STATUS);
+#if NET_DEBUG > 4
+ printk(KERN_DEBUG "%s: CU went non-active (status = %08x)\n", dev->name, status);
+#endif
+ PRIV(dev)->started=1;
+ outw_p(lp->tx_link,ioaddr+SCB_CBL);
+ outw_p(PRIV(dev)->rx_buf_start,ioaddr+SCB_RFA);
+ ack_cmd |= SCB_CUstart | SCB_RUstart;
+ }
+ else if (PRIV(dev)->started)
+ {
+ unsigned short txstatus;
+ txstatus = eexp_hw_lasttxstat(dev);
+ }
+
+ if (SCB_rxdframe(status))
+ {
+ eexp_hw_rx(dev);
+ }
+
+ if ((PRIV(dev)->started&2)!=0 && SCB_RUstat(status)!=4)
+ {
+ printk(KERN_WARNING "%s: RU stopped status %04x, restarting...\n",
+ dev->name,status);
+ lp->stats.rx_errors++;
+ eexp_hw_rxinit(dev);
+ outw(PRIV(dev)->rx_buf_start,ioaddr+SCB_RFA);
+ ack_cmd |= SCB_RUstart;
+ }
+ else if (PRIV(dev)->started==1 && SCB_RUstat(status)==4)
+ PRIV(dev)->started|=2;
+
+ outw(ack_cmd,ioaddr+SCB_CMD);
+ outb(0,ioaddr+SIGNAL_CA);
+ outw(old_rp,ioaddr+READ_PTR);
+ outw(old_wp,ioaddr+WRITE_PTR);
+ outb(SIRQ_en|irqrmap[irq],ioaddr+SET_IRQ);
+ dev->interrupt = 0;
+#if NET_DEBUG > 6
+ printk(KERN_DEBUG "%s: leaving eexp_irq()\n", dev->name);
+#endif
+ return;
+}
+
+/*
+ * Hardware access functions
+ */
+
+/*
+ * Check all the receive buffers, and hand any received packets
+ * to the upper levels. Basic sanity check on each frame
+ * descriptor
+ */
+
+static void eexp_hw_rx(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ unsigned short ioaddr = dev->base_addr;
+ unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+ unsigned short old_rp = inw(ioaddr+READ_PTR);
+ unsigned short rx_block = lp->rx_first;
+ unsigned short boguscount = lp->num_rx_bufs;
+
+#if NET_DEBUG > 6
+ printk(KERN_DEBUG "%s: eexp_hw_rx()\n", dev->name);
+#endif
+
+ while (outw(rx_block,ioaddr+READ_PTR),boguscount--)
+ {
+ unsigned short status = inw(ioaddr);
+ unsigned short rfd_cmd = inw(ioaddr);
+ unsigned short rx_next = inw(ioaddr);
+ unsigned short pbuf = inw(ioaddr);
+ unsigned short pkt_len;
+
+ if (FD_Done(status))
+ {
+ outw(pbuf,ioaddr+READ_PTR);
+ pkt_len = inw(ioaddr);
+
+ if (rfd_cmd!=0x0000 || pbuf!=rx_block+0x16
+ || (pkt_len & 0xc000)!=0xc000)
+ {
+ printk(KERN_WARNING "%s: Rx frame at %04x corrupted, status %04x, cmd %04x, "
+ "next %04x, pbuf %04x, len %04x\n",dev->name,rx_block,
+ status,rfd_cmd,rx_next,pbuf,pkt_len);
+ boguscount++;
+ continue;
+ }
+ else if (!FD_OK(status))
+ {
+ lp->stats.rx_errors++;
+ if (FD_CRC(status))
+ lp->stats.rx_crc_errors++;
+ if (FD_Align(status))
+ lp->stats.rx_frame_errors++;
+ if (FD_Resrc(status))
+ lp->stats.rx_fifo_errors++;
+ if (FD_DMA(status))
+ lp->stats.rx_over_errors++;
+ if (FD_Short(status))
+ lp->stats.rx_length_errors++;
+ }
+ else
+ {
+ struct sk_buff *skb;
+ pkt_len &= 0x3fff;
+ skb = dev_alloc_skb(pkt_len+16);
+ if (skb == NULL)
+ {
+ printk(KERN_WARNING "%s: Memory squeeze, dropping packet\n",dev->name);
+ lp->stats.rx_dropped++;
+ break;
+ }
+ skb->dev = dev;
+ skb_reserve(skb, 2);
+ outw(pbuf+10,ioaddr+READ_PTR);
+ insw(ioaddr,skb_put(skb,pkt_len),(pkt_len+1)>>1);
+ skb->protocol = eth_type_trans(skb,dev);
+ netif_rx(skb);
+ lp->stats.rx_packets++;
+ }
+ outw(rx_block,ioaddr+WRITE_PTR);
+ outw(0x0000,ioaddr);
+ outw(0x0000,ioaddr);
+ }
+ rx_block = rx_next;
+ }
+ outw(old_rp,ioaddr+READ_PTR);
+ outw(old_wp,ioaddr+WRITE_PTR);
+}
+
+/*
+ * Hand a packet to the card for transmission
+ * If we get here, we MUST have already checked
+ * to make sure there is room in the transmit
+ * buffer region
+ */
+
+static void eexp_hw_tx(struct device *dev, unsigned short *buf, unsigned short len)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ unsigned short ioaddr = dev->base_addr;
+ unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+
+ outw(lp->tx_head,ioaddr+WRITE_PTR);
+ outw(0x0000,ioaddr);
+ outw(Cmd_INT|Cmd_Xmit,ioaddr);
+ outw(lp->tx_head+0x08,ioaddr);
+ outw(lp->tx_head+0x0e,ioaddr);
+ outw(0x0000,ioaddr);
+ outw(0x0000,ioaddr);
+ outw(lp->tx_head+0x08,ioaddr);
+ outw(0x8000|len,ioaddr);
+ outw(-1,ioaddr);
+ outw(lp->tx_head+0x16,ioaddr);
+ outw(0,ioaddr);
+ outsw(ioaddr,buf,(len+1)>>1);
+ outw(lp->tx_tail+0x0c,ioaddr+WRITE_PTR);
+ outw(lp->tx_head,ioaddr);
+ dev->trans_start = jiffies;
+ lp->tx_tail = lp->tx_head;
+ if (lp->tx_head==TX_BUF_START+((lp->num_tx_bufs-1)*TX_BUF_SIZE))
+ lp->tx_head = TX_BUF_START;
+ else
+ lp->tx_head += TX_BUF_SIZE;
+ if (lp->tx_head != lp->tx_reap)
+ dev->tbusy = 0;
+ outw(old_wp,ioaddr+WRITE_PTR);
+}
+
+/*
+ * Sanity check the suspected EtherExpress card
+ * Read hardware address, reset card, size memory and
+ * initialize buffer memory pointers. These should
+ * probably be held in dev->priv, in case someone has 2
+ * differently configured cards in their box (Arghhh!)
+ */
+
+static int eexp_hw_probe(struct device *dev, unsigned short ioaddr)
+{
+ unsigned short hw_addr[3];
+ int i;
+ unsigned char *chw_addr = (unsigned char *)hw_addr;
+
+ printk("%s: EtherExpress at %#x, ",dev->name,ioaddr);
+
+ hw_addr[0] = eexp_hw_readeeprom(ioaddr,2);
+ hw_addr[1] = eexp_hw_readeeprom(ioaddr,3);
+ hw_addr[2] = eexp_hw_readeeprom(ioaddr,4);
+
+ /* Standard Address or Compaq LTE Address */
+ if (!((hw_addr[2]==0x00aa && ((hw_addr[1] & 0xff00)==0x0000)) ||
+ (hw_addr[2]==0x0080 && ((hw_addr[1] & 0xff00)==0x5F00))))
+ {
+ printk("rejected: invalid address %04x%04x%04x\n",
+ hw_addr[2],hw_addr[1],hw_addr[0]);
+ return -ENODEV;
+ }
+
+ dev->base_addr = ioaddr;
+ for ( i=0 ; i<6 ; i++ )
+ dev->dev_addr[i] = chw_addr[5-i];
+
+ {
+ char irqmap[]={0, 9, 3, 4, 5, 10, 11, 0};
+ char *ifmap[]={"AUI", "BNC", "10baseT"};
+ enum iftype {AUI=0, BNC=1, TP=2};
+ unsigned short setupval = eexp_hw_readeeprom(ioaddr,0);
+
+ dev->irq = irqmap[setupval>>13];
+ dev->if_port = !(setupval & 0x1000) ? AUI :
+ eexp_hw_readeeprom(ioaddr,5) & 0x1 ? TP : BNC;
+
+ printk("IRQ %d, Interface %s, ",dev->irq,ifmap[dev->if_port]);
+
+ outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+ outb(0,ioaddr+SET_IRQ);
+ }
+
+ dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL);
+ if (!dev->priv)
+ return -ENOMEM;
+
+ memset(dev->priv, 0, sizeof(struct net_local));
+
+ eexp_hw_ASICrst(dev);
+
+ {
+ unsigned short i586mso = 0x023e;
+ unsigned short old_wp,old_rp,old_a0,old_a1;
+ unsigned short a0_0,a1_0,a0_1,a1_1;
+
+ old_wp = inw(ioaddr+WRITE_PTR);
+ old_rp = inw(ioaddr+READ_PTR);
+ outw(0x8000+i586mso,ioaddr+READ_PTR);
+ old_a1 = inw(ioaddr);
+ outw(i586mso,ioaddr+READ_PTR);
+ old_a0 = inw(ioaddr);
+ outw(i586mso,ioaddr+WRITE_PTR);
+ outw(0x55aa,ioaddr);
+ outw(i586mso,ioaddr+READ_PTR);
+ a0_0 = inw(ioaddr);
+ outw(0x8000+i586mso,ioaddr+WRITE_PTR);
+ outw(0x5a5a,ioaddr);
+ outw(0x8000+i586mso,ioaddr+READ_PTR);
+ a1_0 = inw(ioaddr);
+ outw(i586mso,ioaddr+READ_PTR);
+ a0_1 = inw(ioaddr);
+ outw(i586mso,ioaddr+WRITE_PTR);
+ outw(0x1234,ioaddr);
+ outw(0x8000+i586mso,ioaddr+READ_PTR);
+ a1_1 = inw(ioaddr);
+
+ if ((a0_0 != a0_1) || (a1_0 != a1_1) ||
+ (a1_0 != 0x5a5a) || (a0_0 != 0x55aa))
+ {
+ printk("32k\n");
+ PRIV(dev)->rx_buf_end = 0x7ff6;
+ PRIV(dev)->num_tx_bufs = 4;
+ }
+ else
+ {
+ printk("64k\n");
+ PRIV(dev)->num_tx_bufs = 8;
+ PRIV(dev)->rx_buf_start = TX_BUF_START + (PRIV(dev)->num_tx_bufs*TX_BUF_SIZE);
+ PRIV(dev)->rx_buf_end = 0xfff6;
+ }
+
+ outw(0x8000+i586mso,ioaddr+WRITE_PTR);
+ outw(old_a1,ioaddr);
+ outw(i586mso,ioaddr+WRITE_PTR);
+ outw(old_a0,ioaddr);
+ outw(old_wp,ioaddr+WRITE_PTR);
+ outw(old_rp,ioaddr+READ_PTR);
+ }
+
+ if (net_debug)
+ printk(version);
+ dev->open = eexp_open;
+ dev->stop = eexp_close;
+ dev->hard_start_xmit = eexp_xmit;
+ dev->get_stats = eexp_stats;
+ dev->set_multicast_list = &eexp_set_multicast;
+ ether_setup(dev);
+ return 0;
+}
+
+/*
+ * Read a word from eeprom location (0-63?)
+ */
+static unsigned short eexp_hw_readeeprom(unsigned short ioaddr, unsigned char location)
+{
+ unsigned short cmd = 0x180|(location&0x7f);
+ unsigned short rval = 0,wval = EC_CS|i586_RST;
+ int i;
+
+ outb(EC_CS|i586_RST,ioaddr+EEPROM_Ctrl);
+ for ( i=0x100 ; i ; i>>=1 )
+ {
+ if (cmd&i)
+ wval |= EC_Wr;
+ else
+ wval &= ~EC_Wr;
+
+ outb(wval,ioaddr+EEPROM_Ctrl);
+ outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
+ eeprom_delay();
+ outb(wval,ioaddr+EEPROM_Ctrl);
+ eeprom_delay();
+ }
+ wval &= ~EC_Wr;
+ outb(wval,ioaddr+EEPROM_Ctrl);
+ for ( i=0x8000 ; i ; i>>=1 )
+ {
+ outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
+ eeprom_delay();
+ if (inb(ioaddr+EEPROM_Ctrl)&EC_Rd)
+ rval |= i;
+ outb(wval,ioaddr+EEPROM_Ctrl);
+ eeprom_delay();
+ }
+ wval &= ~EC_CS;
+ outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
+ eeprom_delay();
+ outb(wval,ioaddr+EEPROM_Ctrl);
+ eeprom_delay();
+ return rval;
+}
+
+/*
+ * Reap tx buffers and return last transmit status.
+ * if ==0 then either:
+ * a) we're not transmitting anything, so why are we here?
+ * b) we've died.
+ * otherwise, Stat_Busy(return) means we've still got some packets
+ * to transmit, Stat_Done(return) means our buffers should be empty
+ * again
+ */
+
+static unsigned short eexp_hw_lasttxstat(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ unsigned short ioaddr = dev->base_addr;
+ unsigned short old_rp = inw(ioaddr+READ_PTR);
+ unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+ unsigned short tx_block = lp->tx_reap;
+ unsigned short status;
+
+ if (!test_bit(0,(void *)&dev->tbusy) && lp->tx_head==lp->tx_reap)
+ return 0x0000;
+
+ do
+ {
+ outw(tx_block,ioaddr+READ_PTR);
+ status = inw(ioaddr);
+ if (!Stat_Done(status))
+ {
+ lp->tx_link = tx_block;
+ outw(old_rp,ioaddr+READ_PTR);
+ outw(old_wp,ioaddr+WRITE_PTR);
+ return status;
+ }
+ else
+ {
+ lp->last_tx_restart = 0;
+ lp->stats.collisions += Stat_NoColl(status);
+ if (!Stat_OK(status))
+ {
+ if (Stat_Abort(status))
+ lp->stats.tx_aborted_errors++;
+ if (Stat_TNoCar(status) || Stat_TNoCTS(status))
+ lp->stats.tx_carrier_errors++;
+ if (Stat_TNoDMA(status))
+ lp->stats.tx_fifo_errors++;
+ }
+ else
+ lp->stats.tx_packets++;
+ }
+ if (tx_block == TX_BUF_START+((lp->num_tx_bufs-1)*TX_BUF_SIZE))
+ lp->tx_reap = tx_block = TX_BUF_START;
+ else
+ lp->tx_reap = tx_block += TX_BUF_SIZE;
+ dev->tbusy = 0;
+ mark_bh(NET_BH);
+ }
+ while (lp->tx_reap != lp->tx_head);
+
+ lp->tx_link = lp->tx_tail + 0x08;
+ outw(old_rp,ioaddr+READ_PTR);
+ outw(old_wp,ioaddr+WRITE_PTR);
+
+ return status;
+}
+
+/*
+ * This should never happen. It is called when some higher
+ * routine detects the CU has stopped, to try to restart
+ * it from the last packet we knew we were working on,
+ * or the idle loop if we had finished for the time.
+ */
+
+static void eexp_hw_txrestart(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ unsigned short ioaddr = dev->base_addr;
+
+ lp->last_tx_restart = lp->tx_link;
+ outw(lp->tx_link,ioaddr+SCB_CBL);
+ outw(SCB_CUstart,ioaddr+SCB_CMD);
+ outw(0,ioaddr+SCB_STATUS);
+ outb(0,ioaddr+SIGNAL_CA);
+
+ {
+ unsigned short boguscount=50,failcount=5;
+ while (!inw(ioaddr+SCB_STATUS))
+ {
+ if (!--boguscount)
+ {
+ if (--failcount)
+ {
+ printk(KERN_WARNING "%s: CU start timed out, status %04x, cmd %04x\n",
+ dev->name, inw(ioaddr+SCB_STATUS), inw(ioaddr+SCB_CMD));
+ outw(lp->tx_link,ioaddr+SCB_CBL);
+ outw(0,ioaddr+SCB_STATUS);
+ outw(SCB_CUstart,ioaddr+SCB_CMD);
+ outb(0,ioaddr+SIGNAL_CA);
+ boguscount = 100;
+ }
+ else
+ {
+ printk(KERN_WARNING "%s: Failed to restart CU, resetting board...\n",dev->name);
+ eexp_hw_init586(dev);
+ dev->tbusy = 0;
+ mark_bh(NET_BH);
+ return;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Writes down the list of transmit buffers into card
+ * memory. Initial separate, repeated transmits link
+ * them into a circular list, such that the CU can
+ * be constantly active, and unlink them as we reap
+ * transmitted packet buffers, so the CU doesn't loop
+ * and endlessly transmit packets. (Try hacking the driver
+ * to send continuous broadcast messages, say ARP requests
+ * on a subnet with Windows boxes running on Novell and
+ * LAN Workplace with EMM386. Amusing to watch them all die
+ * horribly leaving the Linux boxes up!)
+ */
+
+static void eexp_hw_txinit(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ unsigned short ioaddr = dev->base_addr;
+ unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+ unsigned short tx_block = TX_BUF_START;
+ unsigned short curtbuf;
+
+ for ( curtbuf=0 ; curtbuf<lp->num_tx_bufs ; curtbuf++ )
+ {
+ outw(tx_block,ioaddr+WRITE_PTR);
+ outw(0x0000,ioaddr);
+ outw(Cmd_INT|Cmd_Xmit,ioaddr);
+ outw(tx_block+0x08,ioaddr);
+ outw(tx_block+0x0e,ioaddr);
+ outw(0x0000,ioaddr);
+ outw(0x0000,ioaddr);
+ outw(tx_block+0x08,ioaddr);
+ outw(0x8000,ioaddr);
+ outw(-1,ioaddr);
+ outw(tx_block+0x16,ioaddr);
+ outw(0x0000,ioaddr);
+ tx_block += TX_BUF_SIZE;
+ }
+ lp->tx_head = TX_BUF_START;
+ lp->tx_reap = TX_BUF_START;
+ lp->tx_tail = tx_block - TX_BUF_SIZE;
+ lp->tx_link = lp->tx_tail + 0x08;
+ lp->rx_buf_start = tx_block;
+ outw(old_wp,ioaddr+WRITE_PTR);
+}
+
+/* is this a standard test pattern, or dbecker randomness? */
+
+unsigned short rx_words[] =
+{
+ 0xfeed,0xf00d,0xf001,0x0505,0x2424,0x6565,0xdeaf
+};
+
+/*
+ * Write the circular list of receive buffer descriptors to
+ * card memory. Note, we no longer mark the end of the list,
+ * so if all the buffers fill up, the 82586 will loop until
+ * we free one. This may sound dodgy, but it works, and
+ * it makes the error detection in the interrupt handler
+ * a lot simpler.
+ */
+
+static void eexp_hw_rxinit(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ unsigned short ioaddr = dev->base_addr;
+ unsigned short old_wp = inw(ioaddr+WRITE_PTR);
+ unsigned short rx_block = lp->rx_buf_start;
+
+ lp->num_rx_bufs = 0;
+ lp->rx_first = rx_block;
+ do
+ {
+ lp->num_rx_bufs++;
+ outw(rx_block,ioaddr+WRITE_PTR);
+ outw(0x0000,ioaddr);
+ outw(0x0000,ioaddr);
+ outw(rx_block+RX_BUF_SIZE,ioaddr);
+ outw(rx_block+0x16,ioaddr);
+ outsw(ioaddr, rx_words, sizeof(rx_words)>>1);
+ outw(0x8000,ioaddr);
+ outw(-1,ioaddr);
+ outw(rx_block+0x20,ioaddr);
+ outw(0x0000,ioaddr);
+ outw(0x8000|(RX_BUF_SIZE-0x20),ioaddr);
+ lp->rx_last = rx_block;
+ rx_block += RX_BUF_SIZE;
+ } while (rx_block <= lp->rx_buf_end-RX_BUF_SIZE);
+
+ outw(lp->rx_last+4,ioaddr+WRITE_PTR);
+ outw(lp->rx_first,ioaddr);
+
+ outw(old_wp,ioaddr+WRITE_PTR);
+}
+
+/*
+ * Reset the 586, fill memory (including calls to
+ * eexp_hw_[(rx)(tx)]init()) unreset, and start
+ * the configuration sequence. We don't wait for this
+ * to finish, but allow the interrupt handler to start
+ * the CU and RU for us. We can't start the receive/
+ * transmission system up before we know that the
+ * hardware is configured correctly
+ */
+static void eexp_hw_init586(struct device *dev)
+{
+ struct net_local *lp = (struct net_local *)dev->priv;
+ unsigned short ioaddr = dev->base_addr;
+
+#if NET_DEBUG > 6
+ printk("%s: eexp_hw_init586()\n", dev->name);
+#endif
+
+ lp->started = 0;
+ set_loopback;
+
+ outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+ outb_p(i586_RST,ioaddr+EEPROM_Ctrl);
+ udelay(2000); /* delay 20ms */
+ {
+ unsigned long ofs;
+ for (ofs = 0; ofs < lp->rx_buf_end; ofs += 32) {
+ unsigned long i;
+ outw_p(ofs, ioaddr+SM_PTR);
+ for (i = 0; i < 16; i++) {
+ outw_p(0, ioaddr+SM_ADDR(i<<1));
+ }
+ }
+ }
+
+ outw_p(lp->rx_buf_end,ioaddr+WRITE_PTR);
+ start_code[28] = (dev->flags & IFF_PROMISC)?(start_code[28] | 1):(start_code[28] & ~1);
+ lp->promisc = dev->flags & IFF_PROMISC;
+ /* We may die here */
+ outsw(ioaddr, start_code, sizeof(start_code)>>1);
+ outw(CONF_HW_ADDR,ioaddr+WRITE_PTR);
+ outsw(ioaddr,dev->dev_addr,3);
+ eexp_hw_txinit(dev);
+ eexp_hw_rxinit(dev);
+ outw(0,ioaddr+WRITE_PTR);
+ outw(1,ioaddr);
+ outb(0,ioaddr+EEPROM_Ctrl);
+ outw(0,ioaddr+SCB_CMD);
+ outb(0,ioaddr+SIGNAL_CA);
+ {
+ unsigned short rboguscount=50,rfailcount=5;
+ while (outw(0,ioaddr+READ_PTR),inw(ioaddr))
+ {
+ if (!--rboguscount)
+ {
+ printk(KERN_WARNING "%s: i82586 reset timed out, kicking...\n",
+ dev->name);
+ outw(0,ioaddr+SCB_CMD);
+ outb(0,ioaddr+SIGNAL_CA);
+ rboguscount = 100;
+ if (!--rfailcount)
+ {
+ printk(KERN_WARNING "%s: i82586 not responding, giving up.\n",
+ dev->name);
+ return;
+ }
+ }
+ }
+ }
+
+ outw(CONF_LINK,ioaddr+SCB_CBL);
+ outw(0,ioaddr+SCB_STATUS);
+ outw(0xf000|SCB_CUstart,ioaddr+SCB_CMD);
+ outb(0,ioaddr+SIGNAL_CA);
+ {
+ unsigned short iboguscount=50,ifailcount=5;
+ while (!inw(ioaddr+SCB_STATUS))
+ {
+ if (!--iboguscount)
+ {
+ if (--ifailcount)
+ {
+ printk(KERN_WARNING "%s: i82586 initialization timed out, status %04x, cmd %04x\n",
+ dev->name, inw(ioaddr+SCB_STATUS), inw(ioaddr+SCB_CMD));
+ outw(CONF_LINK,ioaddr+SCB_CBL);
+ outw(0,ioaddr+SCB_STATUS);
+ outw(0xf000|SCB_CUstart,ioaddr+SCB_CMD);
+ outb(0,ioaddr+SIGNAL_CA);
+ iboguscount = 100;
+ }
+ else
+ {
+ printk(KERN_WARNING "%s: Failed to initialize i82586, giving up.\n",dev->name);
+ return;
+ }
+ }
+ }
+ }
+
+ outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
+ clear_loopback;
+ lp->init_time = jiffies;
+#if NET_DEBUG > 6
+ printk("%s: leaving eexp_hw_init586()\n", dev->name);
+#endif
+ return;
+}
+
+/*
+ * completely reset the EtherExpress hardware. We will most likely get
+ * an interrupt during this whether we want one or not. It is best,
+ * therefore, to call this while we don't have a request_irq() on.
+ */
+
+static void eexp_hw_ASICrst(struct device *dev)
+{
+ unsigned short ioaddr = dev->base_addr;
+ unsigned short wrval = 0x0001,succount=0,boguscount=500;
+
+ outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
+
+ PRIV(dev)->started = 0;
+ outb(ASIC_RST|i586_RST,ioaddr+EEPROM_Ctrl);
+ while (succount<20)
+ {
+ if (wrval == 0xffff)
+ wrval = 0x0001;
+ outw(0,ioaddr+WRITE_PTR);
+ outw(wrval,ioaddr);
+ outw(0,ioaddr+READ_PTR);
+ if (wrval++ == inw(ioaddr))
+ succount++;
+ else
+ {
+ succount = 0;
+ if (!boguscount--)
+ {
+ boguscount = 500;
+ printk("%s: Having problems resetting EtherExpress ASIC, continuing...\n",
+ dev->name);
+ wrval = 0x0001;
+ outb(ASIC_RST|i586_RST,ioaddr+EEPROM_Ctrl);
+ }
+ }
+ }
+ outb(i586_RST,ioaddr+EEPROM_Ctrl);
+}
+
+
+/*
+ * Set or clear the multicast filter for this adaptor.
+ * We have to do a complete 586 restart for this to take effect.
+ * At the moment only promiscuous mode is supported.
+ */
+static void
+eexp_set_multicast(struct device *dev)
+{
+ if ((dev->flags & IFF_PROMISC) != PRIV(dev)->promisc)
+ eexp_hw_init586(dev);
+}
+
+
+/*
+ * MODULE stuff
+ */
+#ifdef MODULE
+
+#define EEXP_MAX_CARDS 4 /* max number of cards to support */
+#define NAMELEN 8 /* max length of dev->name (inc null) */
+
+static char namelist[NAMELEN * EEXP_MAX_CARDS] = { 0, };
+
+static struct device dev_eexp[EEXP_MAX_CARDS] =
+{
+ { NULL, /* will allocate dynamically */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, express_probe },
+};
+
+int irq[EEXP_MAX_CARDS] = {0, };
+int io[EEXP_MAX_CARDS] = {0, };
+
+/* Ideally the user would give us io=, irq= for every card. If any parameters
+ * are specified, we verify and then use them. If no parameters are given, we
+ * autoprobe for one card only.
+ */
+int init_module(void)
+{
+ int this_dev, found = 0;
+
+ for (this_dev = 0; this_dev < EEXP_MAX_CARDS; this_dev++) {
+ struct device *dev = &dev_eexp[this_dev];
+ dev->name = namelist + (NAMELEN*this_dev);
+ dev->irq = irq[this_dev];
+ dev->base_addr = io[this_dev];
+ if (io[this_dev] == 0) {
+ if (this_dev) break;
+ printk(KERN_NOTICE "eexpress.c: Module autoprobe not recommended, give io=xx.\n");
+ }
+ if (register_netdev(dev) != 0) {
+ printk(KERN_WARNING "eexpress.c: Failed to register card at 0x%x.\n", io[this_dev]);
+ if (found != 0) return 0;
+ return -ENXIO;
+ }
+ found++;
+ }
+ return 0;
+}
+
+void cleanup_module(void)
+{
+ int this_dev;
+
+ for (this_dev = 0; this_dev < EEXP_MAX_CARDS; this_dev++) {
+ struct device *dev = &dev_eexp[this_dev];
+ if (dev->priv != NULL) {
+ kfree(dev->priv);
+ dev->priv = NULL;
+ release_region(dev->base_addr, EEXP_IO_EXTENT);
+ unregister_netdev(dev);
+ }
+ }
+}
+#endif
+
+/*
+ * Local Variables:
+ * c-file-style: "linux"
+ * tab-width: 8
+ * compile-command: "gcc -D__KERNEL__ -I/discs/bibble/src/linux-1.3.69/include -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strength-reduce -pipe -m486 -DCPU=486 -DMODULE -c 3c505.c"
+ * End:
+ */